feat(brand): 三站替换四色火焰 logo 与 favicon
官网/docs 嵌入新品牌图;sparkapi nginx 可在未重建镜像时提供 /favicon.ico 与页头 logo。 Co-authored-by: Cursor <cursoragent@cursor.com>
BIN
deploy/brand/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
deploy/brand/favicon-16.png
Normal file
|
After Width: | Height: | Size: 690 B |
BIN
deploy/brand/favicon-32.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
deploy/brand/favicon.ico
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
deploy/brand/logo-256.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
deploy/brand/logo.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
@ -67,11 +67,33 @@ server {
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
}
|
||||
|
||||
# Brand icons (no app rebuild required)
|
||||
location = /favicon.ico {
|
||||
alias /var/www/sparkapi-brand/favicon.ico;
|
||||
add_header Cache-Control "public, max-age=604800";
|
||||
access_log off;
|
||||
}
|
||||
location = /apple-touch-icon.png {
|
||||
alias /var/www/sparkapi-brand/apple-touch-icon.png;
|
||||
add_header Cache-Control "public, max-age=604800";
|
||||
access_log off;
|
||||
}
|
||||
location ^~ /brand/ {
|
||||
alias /var/www/sparkapi-brand/;
|
||||
add_header Cache-Control "public, max-age=604800";
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://spark_http;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
# Inject favicon + header logo until next spark-server image ships docs HTML.
|
||||
sub_filter_once off;
|
||||
sub_filter_types text/html;
|
||||
sub_filter '<head>' '<head><link rel="icon" href="/favicon.ico?v=20260924-flames" sizes="any"/><link rel="apple-touch-icon" href="/apple-touch-icon.png?v=20260924-flames"/>';
|
||||
sub_filter '<strong>Spark IM</strong>' '<a href="/docs" style="display:inline-flex;align-items:center;gap:8px;color:inherit;text-decoration:none"><img src="/brand/logo.png?v=20260924-flames" width="28" height="28" alt="" style="border-radius:6px"/><strong>Spark IM</strong></a>';
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,8 @@ func authLimitPath(path string) bool {
|
||||
|
||||
func publicPath(path string) bool {
|
||||
switch path {
|
||||
case "/healthz", "/readyz", "/openapi.yaml", "/docs", "/docs/":
|
||||
case "/healthz", "/readyz", "/openapi.yaml", "/docs", "/docs/",
|
||||
"/favicon.ico", "/apple-touch-icon.png":
|
||||
return true
|
||||
case "/v1/meta/enums", "/v1/auth/login", "/v1/auth/token/refresh", "/v1/sms/codes",
|
||||
"/v1/music/playlist", "/v1/music/settings":
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@ -19,4 +22,52 @@ func registerDocs(r *gin.Engine) {
|
||||
r.GET("/docs/", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusFound, "/docs")
|
||||
})
|
||||
|
||||
staticRoot, err := fs.Sub(docsStatic, "docs/static")
|
||||
if err != nil {
|
||||
panic("docs static embed: " + err.Error())
|
||||
}
|
||||
serveStaticFile := func(name, contentType string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
b, err := fs.ReadFile(staticRoot, name)
|
||||
if err != nil {
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
c.Header("Cache-Control", "public, max-age=604800")
|
||||
c.Data(http.StatusOK, contentType, b)
|
||||
}
|
||||
}
|
||||
r.GET("/favicon.ico", serveStaticFile("favicon.ico", "image/x-icon"))
|
||||
r.GET("/apple-touch-icon.png", serveStaticFile("apple-touch-icon.png", "image/png"))
|
||||
r.GET("/docs/static/*filepath", func(c *gin.Context) {
|
||||
rel := strings.TrimPrefix(c.Param("filepath"), "/")
|
||||
rel = path.Clean("/" + rel)
|
||||
rel = strings.TrimPrefix(rel, "/")
|
||||
if rel == "" || strings.Contains(rel, "..") {
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
b, err := fs.ReadFile(staticRoot, rel)
|
||||
if err != nil {
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
ct := contentTypeFor(rel)
|
||||
c.Header("Cache-Control", "public, max-age=604800")
|
||||
c.Data(http.StatusOK, ct, b)
|
||||
})
|
||||
}
|
||||
|
||||
func contentTypeFor(name string) string {
|
||||
switch strings.ToLower(path.Ext(name)) {
|
||||
case ".png":
|
||||
return "image/png"
|
||||
case ".ico":
|
||||
return "image/x-icon"
|
||||
case ".svg":
|
||||
return "image/svg+xml"
|
||||
default:
|
||||
return "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,9 @@
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Spark IM API</title>
|
||||
<link rel="icon" href="/favicon.ico?v=20260924-flames" sizes="any"/>
|
||||
<link rel="icon" href="/docs/static/favicon-32.png?v=20260924-flames" type="image/png" sizes="32x32"/>
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png?v=20260924-flames"/>
|
||||
<script>
|
||||
(function () {
|
||||
var p = location.pathname.replace(/\/+$/, "") || "/docs";
|
||||
@ -21,6 +24,13 @@
|
||||
padding: 10px 20px; border-bottom: 1px solid #e0e0e0; background: #fafafa;
|
||||
position: sticky; top: 0; z-index: 20;
|
||||
}
|
||||
.docs-brand {
|
||||
display: inline-flex; align-items: center; gap: 8px; color: #3b4151;
|
||||
font-weight: 700; text-decoration: none;
|
||||
}
|
||||
.docs-brand img {
|
||||
width: 28px; height: 28px; border-radius: 6px; display: block;
|
||||
}
|
||||
.docs-bar strong { color: #3b4151; }
|
||||
.docs-nav { display: flex; gap: 20px; margin: 0; padding-left: 22px; }
|
||||
.docs-nav a { color: #3b4151; text-decoration: none; }
|
||||
@ -98,7 +108,10 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="docs-bar">
|
||||
<strong>Spark IM</strong>
|
||||
<a class="docs-brand" href="/docs">
|
||||
<img src="/docs/static/logo.png?v=20260924-flames" width="28" height="28" alt=""/>
|
||||
<strong>Spark IM</strong>
|
||||
</a>
|
||||
<ol class="docs-nav">
|
||||
<li><a id="nav-api" href="/docs">API</a></li>
|
||||
<li><a id="nav-enums" href="/docs/enums">enums</a></li>
|
||||
|
||||
BIN
internal/transport/http/docs/static/apple-touch-icon.png
vendored
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
internal/transport/http/docs/static/favicon-16.png
vendored
Normal file
|
After Width: | Height: | Size: 690 B |
BIN
internal/transport/http/docs/static/favicon-32.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
internal/transport/http/docs/static/favicon.ico
vendored
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
internal/transport/http/docs/static/logo-256.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
internal/transport/http/docs/static/logo.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
@ -1,7 +1,7 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"embed"
|
||||
)
|
||||
|
||||
//go:embed docs/openapi.yaml
|
||||
@ -9,3 +9,6 @@ var openAPISpec []byte
|
||||
|
||||
//go:embed docs/index.html
|
||||
var docsHTML []byte
|
||||
|
||||
//go:embed docs/static/*
|
||||
var docsStatic embed.FS
|
||||
|
||||
BIN
website/assets/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
website/assets/favicon-16.png
Normal file
|
After Width: | Height: | Size: 690 B |
BIN
website/assets/favicon-32.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
website/assets/favicon.ico
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
website/assets/logo-256.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
website/assets/logo.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
@ -86,9 +86,12 @@ a {
|
||||
}
|
||||
|
||||
.mark-icon {
|
||||
width: 1.35rem;
|
||||
height: 1.35rem;
|
||||
fill: var(--ember);
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
border-radius: 0.4rem;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
box-shadow: 0 0 0 1px rgba(26, 34, 28, 0.08);
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
|
||||
@ -6,7 +6,9 @@
|
||||
<title>Spark Messenger — 即时沟通,火花相连</title>
|
||||
<meta name="description" content="Spark Messenger 是面向好友、群聊与朋友圈的即时通讯应用。安全、简洁、跨端同步。" />
|
||||
<link rel="canonical" href="https://sparkmessenger.wyrs365.com/" />
|
||||
<link rel="icon" href="/assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="icon" href="/assets/favicon.ico?v=20260924-flames" sizes="any" />
|
||||
<link rel="icon" href="/assets/favicon-32.png?v=20260924-flames" type="image/png" sizes="32x32" />
|
||||
<link rel="apple-touch-icon" href="/assets/apple-touch-icon.png?v=20260924-flames" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,500;12..96,700;12..96,800&family=Source+Serif+4:opsz,wght@8..60,400;8..60,600&display=swap" rel="stylesheet" />
|
||||
@ -17,9 +19,7 @@
|
||||
|
||||
<header class="top">
|
||||
<a class="mark" href="/">
|
||||
<svg class="mark-icon" viewBox="0 0 32 32" aria-hidden="true">
|
||||
<path d="M16 2.5 19.8 12.2 30 14.5 21.8 20.8 24.5 30 16 24.6 7.5 30 10.2 20.8 2 14.5 12.2 12.2Z" />
|
||||
</svg>
|
||||
<img class="mark-icon" src="/assets/logo.png?v=20260924-flames" width="32" height="32" alt="" />
|
||||
<span>Spark Messenger</span>
|
||||
</a>
|
||||
<nav class="top-nav" aria-label="主导航">
|
||||
|
||||