mirror of
https://github.com/yusing/godoxy.git
synced 2025-07-21 20:04:03 +02:00
feat(middleware): add themed
middleware with customizable themes and styles
- Introduced a new themed middleware that allows for dynamic theme application. - Added support for multiple themes: dark, dark-grey, solarized-dark, and custom CSS. - Included CSS files for each theme and a font CSS template for font customization. - Updated middleware registry to include the new themed middleware.
This commit is contained in:
parent
46c7ee4d84
commit
e02cacdf2a
6 changed files with 499 additions and 0 deletions
|
@ -25,6 +25,7 @@ var allMiddlewares = map[string]*Middleware{
|
||||||
"hidexforwarded": HideXForwarded,
|
"hidexforwarded": HideXForwarded,
|
||||||
|
|
||||||
"modifyhtml": ModifyHTML,
|
"modifyhtml": ModifyHTML,
|
||||||
|
"themed": Themed,
|
||||||
|
|
||||||
"errorpage": CustomErrorPage,
|
"errorpage": CustomErrorPage,
|
||||||
"customerrorpage": CustomErrorPage,
|
"customerrorpage": CustomErrorPage,
|
||||||
|
|
113
internal/net/gphttp/middleware/themed.go
Normal file
113
internal/net/gphttp/middleware/themed.go
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type themed struct {
|
||||||
|
FontURL string `json:"font_url"`
|
||||||
|
FontFamily string `json:"font_family"`
|
||||||
|
Theme Theme `json:"theme"` // predefined themes
|
||||||
|
CSS string `json:"css"` // css url or content
|
||||||
|
|
||||||
|
m modifyHTML
|
||||||
|
}
|
||||||
|
|
||||||
|
var Themed = NewMiddleware[themed]()
|
||||||
|
|
||||||
|
type Theme string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DarkTheme Theme = "dark"
|
||||||
|
DarkGreyTheme Theme = "dark-grey"
|
||||||
|
SolarizedDarkTheme Theme = "solarized-dark"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed themes/dark.css
|
||||||
|
darkModeCSS string
|
||||||
|
//go:embed themes/dark-grey.css
|
||||||
|
darkGreyModeCSS string
|
||||||
|
//go:embed themes/solarized-dark.css
|
||||||
|
solarizedDarkModeCSS string
|
||||||
|
//go:embed themes/font.css
|
||||||
|
fontCSS string
|
||||||
|
)
|
||||||
|
|
||||||
|
var fontCSSTemplate = template.Must(template.New("fontCSS").Parse(fontCSS))
|
||||||
|
|
||||||
|
const overAllocate = 256
|
||||||
|
|
||||||
|
func (m *themed) before(w http.ResponseWriter, req *http.Request) bool {
|
||||||
|
return m.m.before(w, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *themed) modifyResponse(resp *http.Response) error {
|
||||||
|
return m.m.modifyResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *themed) finalize() error {
|
||||||
|
m.m.Target = "body"
|
||||||
|
if m.FontURL != "" && m.FontFamily != "" {
|
||||||
|
buf := bytes.NewBuffer(bytePool.GetSized(len(fontCSS) + overAllocate))
|
||||||
|
buf.WriteString(`<style type="text/css">`)
|
||||||
|
defer bytePool.Put(buf.Bytes())
|
||||||
|
err := fontCSSTemplate.Execute(buf, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf.WriteString("</style>")
|
||||||
|
m.m.HTML += buf.String()
|
||||||
|
}
|
||||||
|
if m.CSS != "" && m.Theme != "" {
|
||||||
|
return gperr.New("css and theme are mutually exclusive")
|
||||||
|
}
|
||||||
|
// credit: https://hackcss.egoist.dev
|
||||||
|
if m.Theme != "" {
|
||||||
|
switch m.Theme {
|
||||||
|
case DarkTheme:
|
||||||
|
m.m.HTML += wrapStyleTag(darkModeCSS)
|
||||||
|
case DarkGreyTheme:
|
||||||
|
m.m.HTML += wrapStyleTag(darkGreyModeCSS)
|
||||||
|
case SolarizedDarkTheme:
|
||||||
|
m.m.HTML += wrapStyleTag(solarizedDarkModeCSS)
|
||||||
|
default:
|
||||||
|
return gperr.New("invalid theme").Subject(string(m.Theme))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m.CSS != "" {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(m.CSS, "https://"),
|
||||||
|
strings.HasPrefix(m.CSS, "http://"),
|
||||||
|
strings.HasPrefix(m.CSS, "/"):
|
||||||
|
m.m.HTML += wrapStylesheetLinkTag(m.CSS)
|
||||||
|
case strings.HasPrefix(m.CSS, "file://"):
|
||||||
|
css, err := os.ReadFile(strings.TrimPrefix(m.CSS, "file://"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.m.HTML += wrapStyleTag(string(css))
|
||||||
|
default:
|
||||||
|
// inline css
|
||||||
|
m.m.HTML += wrapStyleTag(m.CSS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapStyleTag(css string) string {
|
||||||
|
return fmt.Sprintf(`<style type="text/css">%s</style>`, css)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapStylesheetLinkTag(href string) string {
|
||||||
|
return fmt.Sprintf(`<link rel="stylesheet" href=%q>`, href)
|
||||||
|
}
|
116
internal/net/gphttp/middleware/themes/dark-grey.css
Normal file
116
internal/net/gphttp/middleware/themes/dark-grey.css
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
:not(img, svg) {
|
||||||
|
background-color: #181818 !important;
|
||||||
|
color: #ccc !important;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
background-color: #181818 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
color: #00bcd4 !important;
|
||||||
|
}
|
||||||
|
h1 a,
|
||||||
|
h2 a,
|
||||||
|
h3 a,
|
||||||
|
h4 a,
|
||||||
|
h5 a {
|
||||||
|
color: #ccc !important;
|
||||||
|
}
|
||||||
|
code,
|
||||||
|
strong {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
color: #ccc !important;
|
||||||
|
}
|
||||||
|
table td,
|
||||||
|
table th {
|
||||||
|
border-color: #444 !important;
|
||||||
|
}
|
||||||
|
table tbody td:first-child {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.form-group label {
|
||||||
|
color: #ccc !important;
|
||||||
|
border-color: rgba(95, 95, 95, 0.78) !important;
|
||||||
|
}
|
||||||
|
.form-group.form-textarea label:after {
|
||||||
|
background-color: #181818 !important;
|
||||||
|
}
|
||||||
|
.form-control {
|
||||||
|
color: #ccc !important;
|
||||||
|
border-color: rgba(95, 95, 95, 0.78) !important;
|
||||||
|
}
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #ccc !important;
|
||||||
|
color: #ccc !important;
|
||||||
|
}
|
||||||
|
textarea.form-control {
|
||||||
|
color: #ccc !important;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
border-color: rgba(95, 95, 95, 0.78) !important;
|
||||||
|
}
|
||||||
|
.card .card-header {
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: #ccc !important;
|
||||||
|
border-bottom: 1px solid rgba(95, 95, 95, 0.78) !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-default {
|
||||||
|
border-color: #ababab !important;
|
||||||
|
color: #ababab !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-default:focus,
|
||||||
|
.btn.btn-ghost.btn-default:hover {
|
||||||
|
border-color: #9c9c9c !important;
|
||||||
|
color: #9c9c9c !important;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-default:focus,
|
||||||
|
.btn.btn-ghost.btn-default:hover {
|
||||||
|
border-color: #e0e0e0 !important;
|
||||||
|
color: #e0e0e0 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-primary:focus,
|
||||||
|
.btn.btn-ghost.btn-primary:hover {
|
||||||
|
border-color: #64b5f6 !important;
|
||||||
|
color: #64b5f6 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-success:focus,
|
||||||
|
.btn.btn-ghost.btn-success:hover {
|
||||||
|
border-color: #81c784 !important;
|
||||||
|
color: #81c784 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-info:focus,
|
||||||
|
.btn.btn-ghost.btn-info:hover {
|
||||||
|
border-color: #4dd0e1 !important;
|
||||||
|
color: #4dd0e1 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-error:focus,
|
||||||
|
.btn.btn-ghost.btn-error:hover {
|
||||||
|
border-color: #e57373 !important;
|
||||||
|
color: #e57373 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-warning:focus,
|
||||||
|
.btn.btn-ghost.btn-warning:hover {
|
||||||
|
border-color: #ffb74d !important;
|
||||||
|
color: #ffb74d !important;
|
||||||
|
}
|
||||||
|
.avatarholder,
|
||||||
|
.placeholder {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border-color: #333 !important;
|
||||||
|
}
|
||||||
|
.menu .menu-item {
|
||||||
|
color: #ccc !important;
|
||||||
|
border-color: rgba(95, 95, 95, 0.78) !important;
|
||||||
|
}
|
||||||
|
.menu .menu-item.active,
|
||||||
|
.menu .menu-item:hover {
|
||||||
|
color: #fff !important;
|
||||||
|
border-color: #ccc !important;
|
||||||
|
}
|
116
internal/net/gphttp/middleware/themes/dark.css
Normal file
116
internal/net/gphttp/middleware/themes/dark.css
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
:not(img, svg) {
|
||||||
|
background-color: #000 !important;
|
||||||
|
color: #ccc !important;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
background-color: #000 !important;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
color: #00bcd4 !important;
|
||||||
|
}
|
||||||
|
h1 a,
|
||||||
|
h2 a,
|
||||||
|
h3 a,
|
||||||
|
h4 a,
|
||||||
|
h5 a {
|
||||||
|
color: #ccc !important;
|
||||||
|
}
|
||||||
|
code,
|
||||||
|
strong {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
color: #ccc !important;
|
||||||
|
}
|
||||||
|
table td,
|
||||||
|
table th {
|
||||||
|
border-color: #444 !important;
|
||||||
|
}
|
||||||
|
table tbody td:first-child {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.form-group label {
|
||||||
|
color: #ccc !important;
|
||||||
|
border-color: rgba(95, 95, 95, 0.78) !important;
|
||||||
|
}
|
||||||
|
.form-group.form-textarea label:after {
|
||||||
|
background-color: #000 !important;
|
||||||
|
}
|
||||||
|
.form-control {
|
||||||
|
color: #ccc !important;
|
||||||
|
border-color: rgba(95, 95, 95, 0.78) !important;
|
||||||
|
}
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #ccc !important;
|
||||||
|
color: #ccc !important;
|
||||||
|
}
|
||||||
|
textarea.form-control {
|
||||||
|
color: #ccc !important;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
border-color: rgba(95, 95, 95, 0.78) !important;
|
||||||
|
}
|
||||||
|
.card .card-header {
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: #ccc !important;
|
||||||
|
border-bottom: 1px solid rgba(95, 95, 95, 0.78) !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-default {
|
||||||
|
border-color: #ababab !important;
|
||||||
|
color: #ababab !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-default:focus,
|
||||||
|
.btn.btn-ghost.btn-default:hover {
|
||||||
|
border-color: #9c9c9c !important;
|
||||||
|
color: #9c9c9c !important;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-default:focus,
|
||||||
|
.btn.btn-ghost.btn-default:hover {
|
||||||
|
border-color: #e0e0e0 !important;
|
||||||
|
color: #e0e0e0 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-primary:focus,
|
||||||
|
.btn.btn-ghost.btn-primary:hover {
|
||||||
|
border-color: #64b5f6 !important;
|
||||||
|
color: #64b5f6 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-success:focus,
|
||||||
|
.btn.btn-ghost.btn-success:hover {
|
||||||
|
border-color: #81c784 !important;
|
||||||
|
color: #81c784 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-info:focus,
|
||||||
|
.btn.btn-ghost.btn-info:hover {
|
||||||
|
border-color: #4dd0e1 !important;
|
||||||
|
color: #4dd0e1 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-error:focus,
|
||||||
|
.btn.btn-ghost.btn-error:hover {
|
||||||
|
border-color: #e57373 !important;
|
||||||
|
color: #e57373 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-warning:focus,
|
||||||
|
.btn.btn-ghost.btn-warning:hover {
|
||||||
|
border-color: #ffb74d !important;
|
||||||
|
color: #ffb74d !important;
|
||||||
|
}
|
||||||
|
.avatarholder,
|
||||||
|
.placeholder {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border-color: #333 !important;
|
||||||
|
}
|
||||||
|
.menu .menu-item {
|
||||||
|
color: #ccc !important;
|
||||||
|
border-color: rgba(95, 95, 95, 0.78) !important;
|
||||||
|
}
|
||||||
|
.menu .menu-item.active,
|
||||||
|
.menu .menu-item:hover {
|
||||||
|
color: #fff !important;
|
||||||
|
border-color: #ccc !important;
|
||||||
|
}
|
4
internal/net/gphttp/middleware/themes/font.css
Normal file
4
internal/net/gphttp/middleware/themes/font.css
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
@import url("{{.FontURL}}");
|
||||||
|
body {
|
||||||
|
font-family: "{{.FontFamily}}", "arial", "roboto" !important;
|
||||||
|
}
|
149
internal/net/gphttp/middleware/themes/solarized-dark.css
Normal file
149
internal/net/gphttp/middleware/themes/solarized-dark.css
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
:not(img, svg) {
|
||||||
|
background-color: #073642 !important;
|
||||||
|
color: #78909c !important;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
background-color: #073642 !important;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
color: #009688 !important;
|
||||||
|
}
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
color: #1e88e5 !important;
|
||||||
|
}
|
||||||
|
h1 a,
|
||||||
|
h2 a,
|
||||||
|
h3 a,
|
||||||
|
h4 a,
|
||||||
|
h5 a,
|
||||||
|
h6 a {
|
||||||
|
color: #1e88e5 !important;
|
||||||
|
border-bottom-color: #1e88e5 !important;
|
||||||
|
}
|
||||||
|
h1 a:hover,
|
||||||
|
h2 a:hover,
|
||||||
|
h3 a:hover,
|
||||||
|
h4 a:hover,
|
||||||
|
h5 a:hover,
|
||||||
|
h6 a:hover {
|
||||||
|
background-color: #1e88e5 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
h1 a,
|
||||||
|
h2 a,
|
||||||
|
h3 a,
|
||||||
|
h4 a,
|
||||||
|
h5 a {
|
||||||
|
color: #78909c !important;
|
||||||
|
}
|
||||||
|
code,
|
||||||
|
strong {
|
||||||
|
color: #90a4ae !important;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
.progress-bar-filled {
|
||||||
|
background-color: #558b2f !important;
|
||||||
|
}
|
||||||
|
.progress-bar-filled:after,
|
||||||
|
.progress-bar-filled:before {
|
||||||
|
color: #90a4ae !important;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
color: #78909c !important;
|
||||||
|
}
|
||||||
|
table td,
|
||||||
|
table th {
|
||||||
|
border-color: #b0bec5 !important;
|
||||||
|
}
|
||||||
|
table tbody td:first-child {
|
||||||
|
color: #b0bec5 !important;
|
||||||
|
}
|
||||||
|
.form-group label {
|
||||||
|
color: #78909c !important;
|
||||||
|
border-color: #90a4ae !important;
|
||||||
|
}
|
||||||
|
.form-group.form-textarea label:after {
|
||||||
|
background-color: #073642 !important;
|
||||||
|
}
|
||||||
|
.form-control {
|
||||||
|
color: #78909c !important;
|
||||||
|
border-color: #90a4ae !important;
|
||||||
|
}
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #cfd8dc !important;
|
||||||
|
color: #cfd8dc !important;
|
||||||
|
}
|
||||||
|
textarea.form-control {
|
||||||
|
color: #78909c !important;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
border-color: #90a4ae !important;
|
||||||
|
}
|
||||||
|
.card .card-header {
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: #78909c !important;
|
||||||
|
border-bottom: 1px solid #90a4ae !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-default {
|
||||||
|
border-color: #607d8b !important;
|
||||||
|
color: #607d8b !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-default:focus,
|
||||||
|
.btn.btn-ghost.btn-default:hover {
|
||||||
|
border-color: #90a4ae !important;
|
||||||
|
color: #90a4ae !important;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-default:focus,
|
||||||
|
.btn.btn-ghost.btn-default:hover {
|
||||||
|
border-color: #e0e0e0 !important;
|
||||||
|
color: #e0e0e0 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-primary:focus,
|
||||||
|
.btn.btn-ghost.btn-primary:hover {
|
||||||
|
border-color: #64b5f6 !important;
|
||||||
|
color: #64b5f6 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-success:focus,
|
||||||
|
.btn.btn-ghost.btn-success:hover {
|
||||||
|
border-color: #81c784 !important;
|
||||||
|
color: #81c784 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-info:focus,
|
||||||
|
.btn.btn-ghost.btn-info:hover {
|
||||||
|
border-color: #4dd0e1 !important;
|
||||||
|
color: #4dd0e1 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-error:focus,
|
||||||
|
.btn.btn-ghost.btn-error:hover {
|
||||||
|
border-color: #e57373 !important;
|
||||||
|
color: #e57373 !important;
|
||||||
|
}
|
||||||
|
.btn.btn-ghost.btn-warning:focus,
|
||||||
|
.btn.btn-ghost.btn-warning:hover {
|
||||||
|
border-color: #ffb74d !important;
|
||||||
|
color: #ffb74d !important;
|
||||||
|
}
|
||||||
|
.avatarholder,
|
||||||
|
.placeholder {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border-color: #90a4ae !important;
|
||||||
|
}
|
||||||
|
.menu .menu-item {
|
||||||
|
color: #78909c !important;
|
||||||
|
border-color: #90a4ae !important;
|
||||||
|
}
|
||||||
|
.menu .menu-item.active,
|
||||||
|
.menu .menu-item:hover {
|
||||||
|
color: #fff !important;
|
||||||
|
border-color: #78909c !important;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue