Add favicon, PWA

Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-09-03 01:57:55 +03:00
parent 4b8c75f1f1
commit e0c1c7e4dd
9 changed files with 56 additions and 8 deletions

BIN
android-chrome-192x192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
android-chrome-512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

BIN
apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

BIN
favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -17,7 +17,7 @@
packages = rec { packages = rec {
slenpaste = pkgs.buildGoModule { slenpaste = pkgs.buildGoModule {
pname = "slenpaste"; pname = "slenpaste";
version = "0.2.0"; version = "0.2.1";
src = ./.; src = ./.;
goPackagePath = "github.com/slendidev/slenpaste"; goPackagePath = "github.com/slendidev/slenpaste";
vendorHash = "sha256-MUvodL6K71SCfxu51T/Ka2/w32Kz+IXem1bYqXQLSaU="; vendorHash = "sha256-MUvodL6K71SCfxu51T/Ka2/w32Kz+IXem1bYqXQLSaU=";

61
main.go
View File

@@ -1,6 +1,7 @@
package main package main
import ( import (
"embed"
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
@@ -27,11 +28,19 @@ var (
useHTTPS bool useHTTPS bool
) )
//go:embed android-chrome-192x192.png android-chrome-512x512.png apple-touch-icon.png favicon-16x16.png favicon-32x32.png favicon.ico site.webmanifest
var assetsFS embed.FS
type meta struct { type meta struct {
Expiry time.Time `json:"expiry"` Expiry time.Time `json:"expiry"`
ExpireOnView bool `json:"expire_on_view"` ExpireOnView bool `json:"expire_on_view"`
} }
func init() {
// Ensure correct types for webmanifest on some systems
_ = mime.AddExtensionType(".webmanifest", "application/manifest+json")
}
func getLimiter(ip string) *rate.Limiter { func getLimiter(ip string) *rate.Limiter {
limMu.Lock() limMu.Lock()
defer limMu.Unlock() defer limMu.Unlock()
@@ -68,7 +77,7 @@ func randomID(n int) string {
} }
func indexHandler(w http.ResponseWriter, r *http.Request) { func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/html") w.Header().Add("Content-Type", "text/html; charset=utf-8")
scheme := "http" scheme := "http"
if useHTTPS { if useHTTPS {
scheme = "https" scheme = "https"
@@ -80,7 +89,17 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>slenpaste</title> <title>slenpaste</title>
<!-- Icons / PWA -->
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="icon" href="/favicon.ico">
<link rel="manifest" href="/site.webmanifest">
<meta name="theme-color" content="#ffffff">
<style> <style>
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; } body { font-family: system-ui, sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
pre { background: #f6f6f6; padding: .75rem 1rem; border-radius: .5rem; overflow: auto; } pre { background: #f6f6f6; padding: .75rem 1rem; border-radius: .5rem; overflow: auto; }
@@ -344,7 +363,7 @@ func uploadHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if n == 0 { if n == 0 {
os.Remove(path) _ = os.Remove(path)
http.Error(w, "Empty upload", http.StatusBadRequest) http.Error(w, "Empty upload", http.StatusBadRequest)
return return
} }
@@ -386,8 +405,8 @@ func viewHandler(w http.ResponseWriter, r *http.Request) {
var m meta var m meta
if err := json.Unmarshal(data, &m); err == nil { if err := json.Unmarshal(data, &m); err == nil {
if !m.Expiry.IsZero() && time.Now().After(m.Expiry) { if !m.Expiry.IsZero() && time.Now().After(m.Expiry) {
os.Remove(path) _ = os.Remove(path)
os.Remove(metaPath) _ = os.Remove(metaPath)
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
@@ -405,7 +424,6 @@ func viewHandler(w http.ResponseWriter, r *http.Request) {
} }
defer f.Close() defer f.Close()
// set correct content type based on extension
ext := filepath.Ext(id) ext := filepath.Ext(id)
mimeType := mime.TypeByExtension(ext) mimeType := mime.TypeByExtension(ext)
if mimeType == "" { if mimeType == "" {
@@ -413,7 +431,26 @@ func viewHandler(w http.ResponseWriter, r *http.Request) {
} }
w.Header().Set("Content-Type", mimeType) w.Header().Set("Content-Type", mimeType)
io.Copy(w, f) _, _ = io.Copy(w, f)
}
func serveEmbedded(embeddedName, contentType string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
b, err := assetsFS.ReadFile(embeddedName)
if err != nil {
http.NotFound(w, r)
return
}
if contentType == "" {
if ct := mime.TypeByExtension(filepath.Ext(embeddedName)); ct != "" {
contentType = ct
}
}
if contentType != "" {
w.Header().Set("Content-Type", contentType)
}
http.ServeContent(w, r, embeddedName, time.Time{}, strings.NewReader(string(b)))
}
} }
func main() { func main() {
@@ -425,6 +462,7 @@ func main() {
flag.BoolVar(&useHTTPS, "https", false, "use https:// in generated URLs") flag.BoolVar(&useHTTPS, "https", false, "use https:// in generated URLs")
flag.Parse() flag.Parse()
// Uploads + index
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost { if r.Method == http.MethodPost {
rateLimitMiddleware(uploadHandler)(w, r) rateLimitMiddleware(uploadHandler)(w, r)
@@ -433,6 +471,15 @@ func main() {
} }
}) })
// Embedded favicon/PWA assets
http.HandleFunc("/favicon.ico", serveEmbedded("favicon.ico", "image/x-icon"))
http.HandleFunc("/apple-touch-icon.png", serveEmbedded("apple-touch-icon.png", "image/png"))
http.HandleFunc("/favicon-16x16.png", serveEmbedded("favicon-16x16.png", "image/png"))
http.HandleFunc("/favicon-32x32.png", serveEmbedded("favicon-32x32.png", "image/png"))
http.HandleFunc("/android-chrome-192x192.png", serveEmbedded("android-chrome-192x192.png", "image/png"))
http.HandleFunc("/android-chrome-512x512.png", serveEmbedded("android-chrome-512x512.png", "image/png"))
http.HandleFunc("/site.webmanifest", serveEmbedded("site.webmanifest", "application/manifest+json"))
fmt.Printf("slenpaste running at http://%s, storing in %s\n", listenAddr, staticDir) fmt.Printf("slenpaste running at http://%s, storing in %s\n", listenAddr, staticDir)
http.ListenAndServe(listenAddr, nil) _ = http.ListenAndServe(listenAddr, nil)
} }

1
site.webmanifest Normal file
View File

@@ -0,0 +1 @@
{"name":"slenpaste","short_name":"slenpaste","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}