fix(ui): restore brand across anonymous and error pages
giteabot / giteabot (push) Canceled after 0s
giteabot backport / giteabot (push) Canceled after 0s
release-nightly-snapcraft / build-and-publish (push) Canceled after 0s
release-nightly / nightly-binary (push) Canceled after 0s
release-nightly / nightly-container (push) Canceled after 0s

Assisted-by: Codex:GPT-5
This commit is contained in:
2026-08-29 19:14:04 +08:00
parent ab55a432ec
commit 9860c79986
8 changed files with 62 additions and 16 deletions
+5
View File
@@ -24,6 +24,11 @@ import (
const tplStatus500 templates.TplName = "status/500"
func renderServerErrorPage(w http.ResponseWriter, req *http.Request, respCode int, tmpl templates.TplName, ctxData map[string]any, plainMsg string) {
if ctxData == nil {
ctxData = map[string]any{}
}
ctxData["ShowNavbarBrand"] = true
acceptsHTML := false
for _, part := range req.Header["Accept"] {
if strings.Contains(part, "text/html") {
+16
View File
@@ -27,12 +27,21 @@ func TestRenderPanicErrorPage(t *testing.T) {
assert.Contains(t, respContent, `class="page-content status-page-500"`)
assert.Contains(t, respContent, `</html>`)
assert.Contains(t, respContent, `lang="en-US"`) // make sure the locale work
assertNavbarBrand(t, respContent)
// the 500 page doesn't have normal pages footer, it makes it easier to distinguish a normal page and a failed page.
// especially when a sub-template causes page error, the HTTP response code is still 200,
// the different "footer" is the only way to know whether a page is fully rendered without error.
assert.False(t, test.IsNormalPageCompleted(respContent))
})
t.Run("ServiceUnavailableHTML", func(t *testing.T) {
w := httptest.NewRecorder()
req := &http.Request{URL: &url.URL{}, Header: http.Header{"Accept": []string{"text/html"}}}
req = req.WithContext(reqctx.NewRequestContextForTest(t.Context()))
renderServiceUnavailable(w, req)
assert.Equal(t, http.StatusServiceUnavailable, w.Code)
assertNavbarBrand(t, w.Body.String())
})
t.Run("Plain", func(t *testing.T) {
w := httptest.NewRecorder()
req := &http.Request{URL: &url.URL{}}
@@ -42,6 +51,13 @@ func TestRenderPanicErrorPage(t *testing.T) {
})
}
func assertNavbarBrand(t *testing.T, content string) {
t.Helper()
assert.Contains(t, content, `class="navbar-brand-copy"`)
assert.Contains(t, content, "荣刻科技")
assert.Contains(t, content, "RONCARVE TECHNOLOGY")
}
func TestMain(m *testing.M) {
unittest.MainTest(m)
}
+1
View File
@@ -166,6 +166,7 @@ func (ctx *Context) notFoundInternal(logMsg string, logErr error) {
ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
ctx.Data["Title"] = "Page Not Found"
ctx.Data["ErrorMsg"] = "" // FIXME: the template never renders this message, need to fix in the future (and show safe messages to end users)
ctx.Data["ShowNavbarBrand"] = true
ctx.HTML(http.StatusNotFound, "status/404")
}
+3 -6
View File
@@ -1,13 +1,10 @@
<nav id="navbar" aria-label="{{ctx.Locale.Tr "aria.navbar"}}">
<div class="navbar-left">
<!-- the logo -->
<a class="item" id="navbar-logo" href="{{AppSubUrl}}/" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home_title"}}{{end}}">
<a class="item navbar-brand-link" id="navbar-logo" href="{{AppSubUrl}}/" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home_title"}}{{end}}">
<img width="30" height="30" src="{{AssetUrlPrefix}}/img/logo.png" alt="{{ctx.Locale.Tr "logo"}}" aria-hidden="true">
{{if or .PageIsSignIn (eq .Title (ctx.Locale.Tr "auth.forgot_password_title")) (eq .Title (ctx.Locale.Tr "auth.reset_password")) (eq .Title (ctx.Locale.Tr "auth.prohibit_login"))}}
<span class="navbar-brand-copy" aria-hidden="true">
<strong>荣刻科技</strong>
<small>RONCARVE TECHNOLOGY</small>
</span>
{{if or (not .IsSigned) .ShowNavbarBrand}}
{{template "base/navbar_brand"}}
{{end}}
</a>
+4
View File
@@ -0,0 +1,4 @@
<span class="navbar-brand-copy" aria-hidden="true">
<strong>荣刻科技</strong>
<small>RONCARVE TECHNOLOGY</small>
</span>
+2 -1
View File
@@ -20,8 +20,9 @@
<nav class="ui secondary menu">
<div class="ui container tw-flex">
<div class="item tw-flex-1">
<a href="{{AppSubUrl}}/" aria-label="{{ctx.Locale.Tr "home_title"}}">
<a class="navbar-brand-link tw-flex tw-items-center" href="{{AppSubUrl}}/" aria-label="{{ctx.Locale.Tr "home_title"}}">
<img width="30" height="30" src="{{AssetUrlPrefix}}/img/logo.png" alt="{{ctx.Locale.Tr "logo"}}" aria-hidden="true">
{{template "base/navbar_brand"}}
</a>
</div>
<div class="item">
+22
View File
@@ -25,6 +25,7 @@ func TestView(t *testing.T) {
t.Run("SecurityHeadersDefaults", testSecurityHeadersDefaults)
t.Run("SiteManifest", testSiteManifest)
t.Run("CurrentURL", testViewPageCurrentURL)
t.Run("NavbarBrandVisibility", testNavbarBrandVisibility)
}
func testRenderFileSVGIsInImgTag(t *testing.T) {
@@ -129,3 +130,24 @@ func testViewPageCurrentURL(t *testing.T) {
MakeRequest(t, NewRequest(t, "GET", "/any-page?k=v"), http.StatusNotFound)
assert.Equal(t, "/subpath/any-page?k=v", currentURL)
}
func testNavbarBrandVisibility(t *testing.T) {
assertBrand := func(t *testing.T, session *TestSession, uri string, status int, visible bool) {
t.Helper()
resp := session.MakeRequest(t, NewRequest(t, "GET", uri), status)
doc := NewHTMLParser(t, resp.Body)
brand := strings.Join(strings.Fields(doc.doc.Find("#navbar-logo .navbar-brand-copy").Text()), " ")
if visible {
assert.Equal(t, "荣刻科技 RONCARVE TECHNOLOGY", brand)
} else {
assert.Empty(t, brand)
}
}
assertBrand(t, emptyTestSession(t), "/user/login", http.StatusOK, true)
assertBrand(t, emptyTestSession(t), "/brand-page-does-not-exist", http.StatusNotFound, true)
session := loginUser(t, "user2")
assertBrand(t, session, "/user2", http.StatusOK, false)
assertBrand(t, session, "/brand-page-does-not-exist", http.StatusNotFound, true)
}
+9 -9
View File
@@ -166,26 +166,26 @@ h4.ui.header .sub.header {
padding: 0.5em 0.75em !important;
color: var(--color-text) !important;
}
#navbar-logo {
.navbar-brand-link {
gap: 10px;
}
#navbar-logo .navbar-brand-copy {
.navbar-brand-link .navbar-brand-copy {
display: inline-flex;
flex-direction: column;
line-height: 1;
}
#navbar-logo .navbar-brand-copy strong {
color: #071a50;
.navbar-brand-link .navbar-brand-copy strong {
color: var(--color-primary-dark-1);
font-size: 18px;
font-weight: 700;
font-weight: var(--font-weight-bold);
letter-spacing: 0.02em;
}
#navbar-logo .navbar-brand-copy small {
.navbar-brand-link .navbar-brand-copy small {
margin-top: 5px;
color: #75809a;
color: var(--color-text-light-2);
font-family: var(--fonts-monospace);
font-size: 8px;
letter-spacing: 0.2em;
@@ -193,11 +193,11 @@ h4.ui.header .sub.header {
}
@media (max-width: 767.98px) {
#navbar-logo .navbar-brand-copy strong {
.navbar-brand-link .navbar-brand-copy strong {
font-size: 16px;
}
#navbar-logo .navbar-brand-copy small {
.navbar-brand-link .navbar-brand-copy small {
font-size: 7px;
}
}