tinyrelaycommit

Commit 411567228bc1

browse files at this commit | history

1diff --git a/internal/webui/browse.html b/internal/webui/browse.html
2index 7de76f6..c760f71 100644
3--- a/internal/webui/browse.html
4+++ b/internal/webui/browse.html
5@@ -18,7 +18,7 @@
6 {{else if .Feed}}<table id="rows">
7 <thead><tr><th>hash</th><th>type</th><th>uploaded</th></tr></thead>
8 <tbody>
9-{{range .Feed}}{{$f := asMap .}}<tr><td><a href="/file?sha={{str (index $f "sha256")}}">{{shortID (index $f "sha256")}}</a></td><td>{{str (index $f "type")}} <small>{{str (index $f "size")}} B</small></td><td>{{when (index $f "uploaded")}}</td></tr>
10+{{range .Feed}}{{$f := asMap .}}<tr><td><a href="/file?sha={{urlquery (str (index $f "sha256"))}}">{{shortID (index $f "sha256")}}</a></td><td>{{str (index $f "type")}} <small>{{str (index $f "size")}} B</small></td><td>{{when (index $f "uploaded")}}</td></tr>
11 {{end}}</tbody>
12 </table>
13 {{else}}<p>No files were reported.</p>
14@@ -27,23 +27,23 @@
15 {{end}}
16
17 {{define "file"}}{{if .Error}}<section><h1>File</h1>{{template "alert" .Error}}</section>
18-{{else}}{{$file := asMap .Event}}{{$hash := or (.Query.Get "hash") (.Query.Get "sha")}}<section>
19+{{else}}{{$file := asMap .Event}}{{$hash := or (.Query.Get "hash") (.Query.Get "sha")}}<section id="file-view">
20 <h1>File</h1>
21 <table id="facts">
22 <tr><th>sha256</th><td><code>{{str (index $file "sha256")}}</code></td></tr>
23 <tr><th>type</th><td>{{str (index $file "type")}}</td></tr>
24 <tr><th>size</th><td>{{str (index $file "size")}} bytes</td></tr>
25 {{with index $file "blossom_uri"}}<tr><th>blossom</th><td><code>{{.}}</code></td></tr>{{end}}
26-<tr><th>download</th><td><a href="/files/raw?hash={{str (index $file "sha256")}}">raw</a></td></tr>
27+<tr><th>download</th><td><a fx-ignore href="/files/raw?hash={{urlquery (str (index $file "sha256"))}}">raw</a></td></tr>
28 </table>
29-</section>
30-{{$type := str (index $file "type")}}{{$raw := printf "/files/raw?hash=%s" (str (index $file "sha256"))}}{{if hasPrefix $type "image/"}}<section id="media"><img src="{{$raw}}" alt=""></section>
31-{{else if hasPrefix $type "video/"}}<section id="media"><video controls src="{{$raw}}"></video></section>
32-{{else if hasPrefix $type "audio/"}}<section id="media"><audio controls src="{{$raw}}"></audio></section>
33-{{else if eq $type "application/pdf"}}<section id="media"><a href="{{$raw}}">Open the PDF</a></section>
34-{{else if index $file "binary"}}<section><p>This is a binary file. Download it to view its contents.</p></section>
35-{{else}}{{sourceHTML .Event}}
36-{{end}}{{if .Actor}}<section><details><summary>Manage file</summary><rpc-form method="deleteblob"><input type="hidden" name="param" value="{{$hash}}"><button>Delete file</button></rpc-form></details></section>{{end}}
37+{{$type := str (index $file "type")}}{{$raw := printf "/files/raw?hash=%s" (urlquery (str (index $file "sha256")))}}{{if hasPrefix $type "image/"}}<div id="media"><img src="{{$raw}}" alt=""></div>
38+{{else if hasPrefix $type "video/"}}<div id="media"><video controls src="{{$raw}}"></video></div>
39+{{else if hasPrefix $type "audio/"}}<div id="media"><audio controls src="{{$raw}}"></audio></div>
40+{{else if eq $type "application/pdf"}}<div id="media"><a fx-ignore href="{{$raw}}">Open the PDF</a></div>
41+{{else if index $file "binary"}}<p id="media">This is a binary file. Download it to view its contents.</p>
42+{{else if index $file "content"}}{{sourceHTML .Event}}
43+{{else}}<p id="media">This file is empty.</p>
44+{{end}}{{if .Actor}}<details id="manage-file"><summary>Manage file</summary><rpc-form method="deleteblob"><input type="hidden" name="param" value="{{$hash}}"><button>Delete file</button></rpc-form></details>{{end}}</section>
45 {{end}}{{end}}
46
47 {{define "sites"}}<section>
48diff --git a/internal/webui/browse_test.go b/internal/webui/browse_test.go
49index 6c76094..56a1a50 100644
50--- a/internal/webui/browse_test.go
51+++ b/internal/webui/browse_test.go
52@@ -104,6 +104,32 @@ func TestBrowseRendersTypedGitRelayPage(t *testing.T) {
53 }
54 }
55
56+func TestFileViewerEscapesHashAndExplainsEmptyFiles(t *testing.T) {
57+ b := &emptyFileBackend{browseBackend: &browseBackend{fakeBackend: fakeBackend{policy: policy.Defaults(strings.Repeat("a", 64))}}}
58+ app, err := New(b, Options{})
59+ if err != nil {
60+ t.Fatal(err)
61+ }
62+ recorder := httptest.NewRecorder()
63+ app.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/file?hash=a%26b", nil))
64+ body := recorder.Body.String()
65+ if recorder.Code != http.StatusOK || !strings.Contains(body, `fx-ignore href="/files/raw?hash=a%26b"`) {
66+ t.Fatalf("file hash was not URL encoded: status=%d body=%s", recorder.Code, body)
67+ }
68+ if !strings.Contains(body, "This file is empty.") {
69+ t.Fatalf("empty file did not get an explanatory preview: %s", body)
70+ }
71+}
72+
73+type emptyFileBackend struct{ *browseBackend }
74+
75+func (b *emptyFileBackend) Query(ctx context.Context, method string, params []json.RawMessage, actor string) (any, error) {
76+ if method == "browsefile" {
77+ return map[string]any{"sha256": "a&b", "type": "text/plain", "content": ""}, nil
78+ }
79+ return b.browseBackend.Query(ctx, method, params, actor)
80+}
81+
82 type typedBrowseBackend struct {
83 *browseBackend
84 page gitrelay.BrowsePage
85diff --git a/internal/webui/public_pages.go b/internal/webui/public_pages.go
86index 7d42160..804d484 100644
87--- a/internal/webui/public_pages.go
88+++ b/internal/webui/public_pages.go
89@@ -111,7 +111,7 @@ func (a *App) siteRows(feed []any) []any {
90 } else if scheme == "wss" {
91 scheme = "https"
92 }
93- rows = append(rows, map[string]any{"id": e.ID, "kind": e.Kind, "author": e.PubKey, "label": label, "paths": len(sites.SitePaths(e)), "created_at": e.CreatedAt, "url": scheme + "://" + label + "." + base.Hostname()})
94+ rows = append(rows, map[string]any{"id": e.ID, "kind": e.Kind, "author": e.PubKey, "label": label, "paths": len(sites.SitePaths(e)), "created_at": e.CreatedAt, "url": scheme + "://" + label + "." + base.Host})
95 }
96 return rows
97 }
98diff --git a/internal/webui/repo.html b/internal/webui/repo.html
99index 7a36ef7..9aad9e7 100644
100--- a/internal/webui/repo.html
101+++ b/internal/webui/repo.html
102@@ -4,7 +4,7 @@
103 </section>
104 {{else if or (eq $view "tree") (eq $view "file")}}<table id="split"><tbody><tr><td id="tree"><header><a href="{{repoURL .Query "tree" ""}}">{{str (index $repo "identifier")}}/</a>{{range $index, $crumb := repoBreadcrumbs .Query}}{{if $index}}{{if $crumb.URL}}<a href="{{$crumb.URL}}">{{$crumb.Label}}</a>{{else}}<b>{{$crumb.Label}}</b>{{end}}/{{end}}{{end}}</header>
105 {{template "repoTree" .}}</td>
106-<td id="preview">{{if eq $view "file"}}<header>{{.Query.Get "path"}} <a href="/repo/raw?owner={{urlquery (.Query.Get "owner")}}&amp;repo={{urlquery (.Query.Get "repo")}}&amp;ref={{urlquery (.Query.Get "ref")}}&amp;path={{urlquery (.Query.Get "path")}}">raw</a></header>
107+<td id="preview">{{if eq $view "file"}}<header>{{.Query.Get "path"}} <a fx-ignore href="/repo/raw?owner={{urlquery (.Query.Get "owner")}}&amp;repo={{urlquery (.Query.Get "repo")}}&amp;ref={{urlquery (.Query.Get "ref")}}&amp;path={{urlquery (.Query.Get "path")}}">raw</a></header>
108 {{if index $repo "binary"}}<p>This is a binary file. Download it to view its contents.</p>
109 {{else if index $repo "content"}}{{sourceHTML .Event}}
110 {{else}}<p>This file is empty.</p>
111diff --git a/internal/webui/shell_test.go b/internal/webui/shell_test.go
112index edfc743..1f66d71 100644
113--- a/internal/webui/shell_test.go
114+++ b/internal/webui/shell_test.go
115@@ -81,6 +81,23 @@ func TestSitesPageListsHostedSites(t *testing.T) {
116 }
117 }
118
119+func TestSitesPagePreservesRelayPortInHostedURL(t *testing.T) {
120+ backend := &portSiteBackend{siteBackend: &siteBackend{fakeBackend: fakeBackend{policy: policy.Defaults(strings.Repeat("a", 64))}}}
121+ app, err := New(backend, Options{})
122+ if err != nil {
123+ t.Fatal(err)
124+ }
125+ recorder := httptest.NewRecorder()
126+ app.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/sites", nil))
127+ if !strings.Contains(recorder.Body.String(), ".relay.example:8787") {
128+ t.Fatalf("hosted URL lost relay port: %s", recorder.Body.String())
129+ }
130+}
131+
132+type portSiteBackend struct{ *siteBackend }
133+
134+func (*portSiteBackend) URL() string { return "http://relay.example:8787" }
135+
136 func TestFeedFiltersNarrowTheQuery(t *testing.T) {
137 filter := map[string]any{"kinds": []int{1, 30023}, "limit": 12}
138 applyFeedFilters(filter, url.Values{"kinds": {"7"}, "author": {strings.Repeat("b", 64)}, "since": {"2026-09-01"}})
139diff --git a/internal/webui/style.css b/internal/webui/style.css
140index c039a03..c913a8d 100644
141--- a/internal/webui/style.css
142+++ b/internal/webui/style.css
143@@ -82,6 +82,7 @@ scroll-box th,json-view th,scroll-box td code,scroll-box td nostr-key,scroll-box
144 dl{margin:.5rem 0}dt{font-weight:600}dd{margin:0 0 .35rem 1rem}
145 #facts{border-collapse:collapse;font:13px var(--mono);width:auto}
146 #facts th{text-align:left;font-weight:normal;color:var(--muted);width:9em;padding:3px 0;border:0}#facts td{padding:3px 0;border:0}
147+#facts td{overflow-wrap:anywhere}
148
149 /* Identity: the relay entry page. */
150 #identity{max-width:720px}#identity>header{border-bottom:1px solid var(--line);padding-bottom:12px;margin-bottom:12px}
151diff --git a/internal/webui/webui.go b/internal/webui/webui.go
152index 21c74f4..6ab88ec 100644
153--- a/internal/webui/webui.go
154+++ b/internal/webui/webui.go
155@@ -445,6 +445,7 @@ func (a *App) eventPage(writer http.ResponseWriter, request *http.Request) {
156 return
157 }
158 data := PageData{Event: result, View: identifier, Tab: "event"}
159+ data.Title = "Event " + shortID(identifier) + " | " + a.backend.Slug()
160 if address {
161 data.Feed = browseRows(result)
162 }
163@@ -535,6 +536,7 @@ func (a *App) browse(writer http.ResponseWriter, request *http.Request) {
164 pageQuery.Set("view", "tree")
165 }
166 data := PageData{Tab: browseTab(path), Feed: browseRows(result), Event: result, Query: pageQuery}
167+ data.Title = browseTitle(path, pageQuery, result, a.backend.Slug())
168 if method == "browserepo" && pageQuery.Get("view") == "home" {
169 data.Readme = a.readme(request.Context(), actor, pageQuery)
170 }
171@@ -542,6 +544,24 @@ func (a *App) browse(writer http.ResponseWriter, request *http.Request) {
172 a.render(writer, request, data)
173 }
174
175+func browseTitle(path string, query url.Values, result any, slug string) string {
176+ label := map[string]string{"/repos": "Repositories", "/files": "Files", "/file": "File"}[path]
177+ if path == "/repo" {
178+ label = query.Get("repo")
179+ if label == "" {
180+ label = plainString(valueMap(result)["identifier"])
181+ }
182+ if label == "" {
183+ label = "Repository"
184+ }
185+ label += " | " + repoView(query)
186+ }
187+ if label == "" {
188+ label = "Browse"
189+ }
190+ return label + " | " + slug
191+}
192+
193 // readme renders README.md from the requested ref for the repository entry
194 // page. A missing or binary README simply yields no content.
195 func (a *App) readme(ctx context.Context, actor string, query url.Values) template.HTML {

About

ownerc01503e8035f
visibilitypublic
head411567228bc1

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

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

Refs