Merge pull request #1 from anhgelus/feat/custom-page
[Feat] Custom page
This commit is contained in:
commit
0149f9905c
7 changed files with 323 additions and 19 deletions
|
@ -176,6 +176,9 @@
|
||||||
"image",
|
"image",
|
||||||
"tags"
|
"tags"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"custom_pages": {
|
||||||
|
"type": "array"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
117
custom_page.schema.json
Normal file
117
custom_page.schema.json
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uri": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tag_hover": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"color",
|
||||||
|
"position"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"angle",
|
||||||
|
"colors"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"text_hover": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"background_hover": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"text",
|
||||||
|
"text_hover",
|
||||||
|
"background",
|
||||||
|
"background_hover"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"text",
|
||||||
|
"tag_hover",
|
||||||
|
"background",
|
||||||
|
"buttons"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"content"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"uri",
|
||||||
|
"image",
|
||||||
|
"description",
|
||||||
|
"colors",
|
||||||
|
"content"
|
||||||
|
]
|
||||||
|
}
|
160
data.go
160
data.go
|
@ -1,11 +1,31 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"github.com/anhgelus/golatt"
|
"github.com/anhgelus/golatt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TitleContentType = "title"
|
||||||
|
SubtitleContentType = "subtitle"
|
||||||
|
ParagraphContentType = "paragraph"
|
||||||
|
ListContentType = "list"
|
||||||
|
OrderedListContentType = "ordered_list"
|
||||||
|
ButtonsContentType = "links"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigData interface {
|
||||||
|
GetTextColor() template.CSS
|
||||||
|
GetBackground() template.CSS
|
||||||
|
GetBackgroundImage() template.CSS
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Image string `json:"image"`
|
Image string `json:"image"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
@ -13,6 +33,7 @@ type Config struct {
|
||||||
Color *Color `json:"colors"`
|
Color *Color `json:"colors"`
|
||||||
Links []*Link `json:"links"`
|
Links []*Link `json:"links"`
|
||||||
Legal *Legal `json:"legal"`
|
Legal *Legal `json:"legal"`
|
||||||
|
CustomPages []string `json:"custom_pages"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Person struct {
|
type Person struct {
|
||||||
|
@ -63,7 +84,62 @@ type Legal struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) GetBackground() template.CSS {
|
func (c *Config) GetBackground() template.CSS {
|
||||||
bg := c.Color.Background
|
return c.Color.GetBackground()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) GetBackgroundImage() template.CSS {
|
||||||
|
return template.CSS("--background-image: url(" + golatt.GetStaticPath(c.Image) + ");")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) GetTextColor() template.CSS {
|
||||||
|
return c.Color.GetTextColor()
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomPage struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
URI string `json:"uri"`
|
||||||
|
Image string `json:"image"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Color *Color `json:"colors"`
|
||||||
|
Content []*CustomContent `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomContent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Content interface {
|
||||||
|
Get() template.HTML
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) LoadCustomPages() ([]*CustomPage, error) {
|
||||||
|
if c.CustomPages == nil {
|
||||||
|
println("null")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var pages []*CustomPage
|
||||||
|
for _, cp := range c.CustomPages {
|
||||||
|
b, err := os.ReadFile(cp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var p CustomPage
|
||||||
|
err = json.Unmarshal(b, &p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pages = append(pages, &p)
|
||||||
|
}
|
||||||
|
return pages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Color) GetTextColor() template.CSS {
|
||||||
|
return template.CSS("--text-color: " + t.Text + ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Color) GetBackground() template.CSS {
|
||||||
|
bg := t.Background
|
||||||
css := "background: " + bg.Type + "-gradient("
|
css := "background: " + bg.Type + "-gradient("
|
||||||
if bg.Type == "linear" {
|
if bg.Type == "linear" {
|
||||||
css += strconv.Itoa(int(bg.Angle)) + "deg,"
|
css += strconv.Itoa(int(bg.Angle)) + "deg,"
|
||||||
|
@ -74,14 +150,6 @@ func (c *Config) GetBackground() template.CSS {
|
||||||
return template.CSS(css[:len(css)-1] + ");")
|
return template.CSS(css[:len(css)-1] + ");")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) GetBackgroundImage() template.CSS {
|
|
||||||
return template.CSS("--background-image: url(" + golatt.GetStaticPath(c.Image) + ");")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) GetTextColor() template.CSS {
|
|
||||||
return template.CSS("--text-color: " + c.Color.Text + ";")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *ButtonColor) GetTextColor() template.CSS {
|
func (b *ButtonColor) GetTextColor() template.CSS {
|
||||||
return template.CSS("--text-color: " + b.Text + ";--text-color-hover: " + b.TextHover + ";")
|
return template.CSS("--text-color: " + b.Text + ";--text-color-hover: " + b.TextHover + ";")
|
||||||
}
|
}
|
||||||
|
@ -93,3 +161,77 @@ func (b *ButtonColor) GetBackground() template.CSS {
|
||||||
func (t *Color) GetTagColor() template.CSS {
|
func (t *Color) GetTagColor() template.CSS {
|
||||||
return template.CSS("--tag-hover: " + t.TagHover + ";")
|
return template.CSS("--tag-hover: " + t.TagHover + ";")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *CustomPage) GetTextColor() template.CSS {
|
||||||
|
return p.Color.GetTextColor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CustomPage) GetBackgroundImage() template.CSS {
|
||||||
|
return template.CSS("--background-image: url(" + golatt.GetStaticPath(p.Image) + ");")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CustomPage) GetBackground() template.CSS {
|
||||||
|
return p.Color.GetBackground()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CustomPage) GetContent() template.HTML {
|
||||||
|
var res template.HTML
|
||||||
|
for _, c := range p.Content {
|
||||||
|
res += c.Get(p)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CustomContent) Get(p *CustomPage) template.HTML {
|
||||||
|
if c.Type == TitleContentType {
|
||||||
|
return template.HTML("<h2>" + c.Content + "</h2>")
|
||||||
|
} else if c.Type == SubtitleContentType {
|
||||||
|
return template.HTML("<h3>" + c.Content + "</h3>")
|
||||||
|
} else if c.Type == ParagraphContentType {
|
||||||
|
return template.HTML("<p>" + c.Content + "</p>")
|
||||||
|
} else if c.Type == ListContentType {
|
||||||
|
v := ""
|
||||||
|
for _, s := range strings.Split(c.Content, "--") {
|
||||||
|
if len(strings.Trim(s, " ")) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v += "<li>" + strings.Trim(s, " ") + "</li>"
|
||||||
|
}
|
||||||
|
return template.HTML("<ul>" + v + "</ul>")
|
||||||
|
} else if c.Type == OrderedListContentType {
|
||||||
|
v := ""
|
||||||
|
for _, s := range strings.Split(c.Content, "--") {
|
||||||
|
if len(strings.TrimSpace(s)) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v += "<li>" + strings.TrimSpace(s) + "</li>"
|
||||||
|
}
|
||||||
|
return template.HTML("<ol>" + v + "</ol>")
|
||||||
|
} else if c.Type == ButtonsContentType {
|
||||||
|
// [Bonsoir](/hello) -- [Bonjour](/not_hello)
|
||||||
|
v := ""
|
||||||
|
for _, s := range strings.Split(c.Content, "--") {
|
||||||
|
if len(strings.TrimSpace(s)) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sp := strings.Split(s, "](")
|
||||||
|
if len(sp) != 2 {
|
||||||
|
slog.Warn("Invalid button", "s", s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
url := strings.TrimSpace(sp[1])
|
||||||
|
v += fmt.Sprintf(
|
||||||
|
`<div class="link"><a href="%s">%s</a></div>`,
|
||||||
|
url[:len(url)-1],
|
||||||
|
strings.TrimSpace(sp[0])[1:],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return template.HTML(fmt.Sprintf(
|
||||||
|
`<nav class="links" style="%s">%s</nav>`,
|
||||||
|
p.Color.Button.GetBackground()+p.Color.Button.GetTextColor(),
|
||||||
|
v,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
slog.Warn("Unknown type", "type", c.Type, "value", c.Content)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
5
index.ts
5
index.ts
|
@ -23,10 +23,7 @@ function changePage(href: string) {
|
||||||
const doc = new DOMParser().parseFromString(html, "text/html")
|
const doc = new DOMParser().parseFromString(html, "text/html")
|
||||||
window.history.pushState({}, "", href)
|
window.history.pushState({}, "", href)
|
||||||
document.title = doc.title
|
document.title = doc.title
|
||||||
const distMain = doc.querySelector("main")
|
document.body = doc.body
|
||||||
const currentMain = document.querySelector("main")
|
|
||||||
if (distMain === null || currentMain === null) document.body = doc.body
|
|
||||||
else currentMain.innerHTML = distMain.innerHTML
|
|
||||||
setupEvents()
|
setupEvents()
|
||||||
scrollTo({
|
scrollTo({
|
||||||
top: 0,
|
top: 0,
|
||||||
|
|
15
main.go
15
main.go
|
@ -50,6 +50,10 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
customPages, err := cfg.LoadCustomPages()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
g := golatt.New(templates)
|
g := golatt.New(templates)
|
||||||
g.DefaultSeoData = &golatt.SeoData{
|
g.DefaultSeoData = &golatt.SeoData{
|
||||||
Image: cfg.Image,
|
Image: cfg.Image,
|
||||||
|
@ -74,6 +78,17 @@ func main() {
|
||||||
&cfg).
|
&cfg).
|
||||||
Handle()
|
Handle()
|
||||||
|
|
||||||
|
for _, cp := range customPages {
|
||||||
|
slog.Info("Creating custom page...", "title", cp.Title, "uri", cp.URI)
|
||||||
|
g.NewTemplate("custom_page",
|
||||||
|
cp.URI,
|
||||||
|
cp.Title,
|
||||||
|
cp.Image,
|
||||||
|
cp.Description,
|
||||||
|
cp).
|
||||||
|
Handle()
|
||||||
|
}
|
||||||
|
|
||||||
g.NotFoundHandler = func(w http.ResponseWriter, r *http.Request) {
|
g.NotFoundHandler = func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
|
|
@ -236,3 +236,22 @@ h4 {
|
||||||
padding: 0 2rem 1rem 2rem;
|
padding: 0 2rem 1rem 2rem;
|
||||||
border-radius: 32px;
|
border-radius: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-page {
|
||||||
|
& h2 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
& h3 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
& ul {
|
||||||
|
list-style: disc inside;
|
||||||
|
}
|
||||||
|
& .links {
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
11
templates/page/custom_page.gohtml
Normal file
11
templates/page/custom_page.gohtml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{{define "body"}}
|
||||||
|
<main class="custom-page" style="{{ .GetBackground }}">
|
||||||
|
<div class="header">
|
||||||
|
<h2>{{ .Title }}</h2>
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
{{ .GetContent }}
|
||||||
|
</main>
|
||||||
|
{{end}}
|
Loading…
Add table
Add a link
Reference in a new issue