Move to embedded dirs
This commit is contained in:
parent
119ae9c3c6
commit
430a7461e6
16 changed files with 99 additions and 42 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,6 +1,20 @@
|
|||
# Changelog
|
||||
This file keeps track of changes in more human-readable fashion
|
||||
|
||||
## v1.0.0
|
||||
This release includes several **breaking** changes
|
||||
* Made a new favicon
|
||||
* English is now used as a fallback language, making incomplete translations somewhat usable
|
||||
* Added a link to the bottom of the page in day list, for when you need to get to footer but been using the app for months...
|
||||
* `pages`, `public` and `i18n` directories now use embed.FS
|
||||
* Running plain executable is now a somewhat valid option.
|
||||
* A problem with this is that languages and themes are now harder to add. I will think about what to do about that, maybe some kind of `custom.css` file.
|
||||
I might also be open to GitHub pull requests adding **some** languages (German and French could be nice starting points, I have friends studying them)
|
||||
* Added a new "sans" theme
|
||||
* Light blue accent colour
|
||||
* Comic Sans MS for *everything*
|
||||
* sorry
|
||||
|
||||
## v0.6.0
|
||||
* Replaced config reload with edit in info (api method still available, config reloads on save)
|
||||
* Bug fixes
|
||||
|
|
|
@ -15,10 +15,7 @@ WORKDIR /
|
|||
# Bring over the executable
|
||||
COPY --from=build-stage /hibiscus /hibiscus
|
||||
|
||||
# Copy files
|
||||
COPY public public/
|
||||
COPY pages pages/
|
||||
COPY i18n i18n/
|
||||
# Data dirs
|
||||
VOLUME /data
|
||||
VOLUME /config
|
||||
|
||||
|
|
|
@ -50,8 +50,8 @@ password=admin # Your password
|
|||
port=7101 # What port to run on (probably leave on 7101 if using docker)
|
||||
timezone=Local # IANA Time zone database identifier ("UTC", Local", "Europe/Moscow" etc.), Defaults to Local if can't parse.
|
||||
grace_period=0s # Time after a new day begins, but before the switch to next day's file. e.g. 2h30m - files will change at 2:30am
|
||||
language=en # ISO-639 language code (pre-installed - en, ru)
|
||||
theme=default # Picked theme (pre-installed - default, gruvbox, high-contrast)
|
||||
language=en # ISO-639 language code (available - en, ru)
|
||||
theme=default # Picked theme (available - default, high-contrast, lavender, gruvbox, sans)
|
||||
title=🌺 Hibiscus.txt # The text in the header
|
||||
log_to_file=false # Whether to write logs to a file
|
||||
log_file=config/log.txt # Where to store the log file if it is enabled
|
||||
|
|
2
TODO.md
2
TODO.md
|
@ -1,6 +1,8 @@
|
|||
# TODO
|
||||
List of things to add to this project
|
||||
|
||||
* Forward/backward buttons for days
|
||||
|
||||
## v1.0.0
|
||||
* a logo so I can enable PWA (and look cool)
|
||||
* Versioned containers via `ghcr.io` or `dockerhub`,
|
||||
|
|
|
@ -144,7 +144,7 @@ func (c *Config) Reload() error {
|
|||
}
|
||||
slog.Debug("reloaded config", "config", c)
|
||||
|
||||
return LoadLanguage(c.Language) // Load selected language
|
||||
return SetLanguage(c.Language) // Load selected language
|
||||
}
|
||||
|
||||
// Read gets raw contents from ConfigFile
|
||||
|
|
33
i18n.go
33
i18n.go
|
@ -1,32 +1,33 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
//go:embed i18n
|
||||
var I18n embed.FS
|
||||
var Translations = map[string]string{}
|
||||
|
||||
// LoadLanguage loads a json file for selected language into the Translations map
|
||||
func LoadLanguage(language string) error {
|
||||
filename := "i18n/" + language + ".json"
|
||||
|
||||
if _, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
// SetLanguage loads a json file for selected language into the Translations map, with english language as a fallback
|
||||
func SetLanguage(language string) error {
|
||||
loadLanguage := func(language string) error {
|
||||
filename := "i18n/" + language + ".json"
|
||||
fileContents, err := I18n.ReadFile(filename)
|
||||
if err != nil {
|
||||
slog.Error("error reading language file",
|
||||
"error", err,
|
||||
"file", filename)
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(fileContents, &Translations)
|
||||
}
|
||||
|
||||
fileContents, err := os.ReadFile(filename)
|
||||
err := loadLanguage("en") // Load english as fallback language
|
||||
if err != nil {
|
||||
slog.Error("error reading file",
|
||||
"error", err,
|
||||
"file", filename)
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(fileContents, &Translations)
|
||||
return err
|
||||
return loadLanguage(language)
|
||||
}
|
||||
|
||||
// TranslatableText attempts to match an id to a string in current language
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"button.notes": "New note",
|
||||
"prompt.notes": "Note name",
|
||||
"noscript.notes": "/notes/<name>",
|
||||
"prompt.days": "To the bottom",
|
||||
|
||||
"info.version": "Version",
|
||||
"info.version.link": "source and changelog",
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"button.notes": "Новая заметка",
|
||||
"prompt.notes": "Название заметки",
|
||||
"noscript.notes": "/notes/<название>",
|
||||
"prompt.days": "Вниз",
|
||||
|
||||
"info.version": "Версия",
|
||||
"info.version.link": "исходный код",
|
||||
|
|
4
info.go
4
info.go
|
@ -6,7 +6,7 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
var infoTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFiles("./pages/base.html", "./pages/info.html"))
|
||||
var infoTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFS(Pages, "pages/base.html", "pages/info.html"))
|
||||
|
||||
type AppInfo struct {
|
||||
Version string
|
||||
|
@ -15,7 +15,7 @@ type AppInfo struct {
|
|||
|
||||
// Info contains app information
|
||||
var Info = AppInfo{
|
||||
Version: "0.6.1",
|
||||
Version: "1.0.0",
|
||||
SourceLink: "https://git.a71.su/Andrew71/hibiscus",
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
{{end}}
|
||||
|
||||
{{define "footer"}}
|
||||
<footer>
|
||||
<footer id="footer">
|
||||
<p><a href="/">{{ translatableText "link.today" }}</a> | <a href="/day">{{ translatableText "link.days" }}</a> | <a href="/notes">{{ translatableText "link.notes" }}</a>
|
||||
<span style="float:right;"><a class="no-accent" href="/info" title="{{ translatableText "link.info" }}">v{{ info.Version }}</a></span></p>
|
||||
</footer>
|
||||
|
|
BIN
public/favicon-512.png
Normal file
BIN
public/favicon-512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 894 B After Width: | Height: | Size: 894 B |
|
@ -4,7 +4,7 @@
|
|||
"description": "A plaintext diary",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/public/TODO.png",
|
||||
"src": "/public/favicon-512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
},
|
||||
|
|
30
public/themes/sans.css
Normal file
30
public/themes/sans.css
Normal file
|
@ -0,0 +1,30 @@
|
|||
/* Sans Undertale */
|
||||
:root {
|
||||
/* Light theme */
|
||||
--text-light: #3c3836;
|
||||
--bg-light: #e2e7e8;
|
||||
|
||||
--clickable-light: #41acda;
|
||||
--clickable-hover-light: #2d8db4;
|
||||
--clickable-label-light: #e2e8e2;
|
||||
--text-hover-light: #665c54;
|
||||
|
||||
--textarea-bg-light: #f3f3f3;
|
||||
--textarea-border-light: #3c3836;
|
||||
|
||||
/* Dark theme */
|
||||
--text-dark: #e2e7e8;
|
||||
--bg-dark: #25282a;
|
||||
|
||||
--clickable-dark: #41acda;
|
||||
--clickable-hover-dark: #2d8db4;
|
||||
--clickable-label-dark: #e2e8e2;
|
||||
--text-hover-dark: #cdd2d3;
|
||||
|
||||
--textarea-bg-dark: #2d3234;
|
||||
--textarea-border-dark: #3c3836;
|
||||
}
|
||||
|
||||
body, textarea, input, button {
|
||||
font-family: "Comic Sans MS", sans-serif;
|
||||
}
|
37
routes.go
37
routes.go
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"errors"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"html/template"
|
||||
|
@ -26,25 +27,40 @@ type Entry struct {
|
|||
|
||||
type formatEntries func([]string) []Entry
|
||||
|
||||
//go:embed public
|
||||
var Public embed.FS
|
||||
|
||||
//go:embed pages
|
||||
var Pages embed.FS
|
||||
|
||||
// EmbeddedFile returns a file in Pages while "handling" potential errors
|
||||
func EmbeddedFile(name string) []byte {
|
||||
data, err := Pages.ReadFile(name)
|
||||
if err != nil {
|
||||
slog.Error("Error embedded file", "err", err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
var templateFuncs = map[string]interface{}{
|
||||
"translatableText": TranslatableText,
|
||||
"info": func() AppInfo { return Info },
|
||||
"config": func() Config { return Cfg },
|
||||
}
|
||||
var editTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFiles("./pages/base.html", "./pages/edit.html"))
|
||||
var viewTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFiles("./pages/base.html", "./pages/entry.html"))
|
||||
var listTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFiles("./pages/base.html", "./pages/list.html"))
|
||||
var editTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFS(Pages, "pages/base.html", "pages/edit.html"))
|
||||
var viewTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFS(Pages, "pages/base.html", "pages/entry.html"))
|
||||
var listTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFS(Pages, "pages/base.html", "pages/list.html"))
|
||||
|
||||
// NotFound returns a user-friendly 404 error page
|
||||
func NotFound(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(404)
|
||||
http.ServeFile(w, r, "./pages/error/404.html")
|
||||
HandleWrite(w.Write(EmbeddedFile("pages/error/404.html")))
|
||||
}
|
||||
|
||||
// InternalError returns a user-friendly 500 error page
|
||||
func InternalError(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(500)
|
||||
http.ServeFile(w, r, "./pages/error/500.html")
|
||||
HandleWrite(w.Write(EmbeddedFile("pages/error/500.html")))
|
||||
}
|
||||
|
||||
// GetToday renders HTML page for today's entry
|
||||
|
@ -97,7 +113,9 @@ func GetEntries(w http.ResponseWriter, r *http.Request, title string, descriptio
|
|||
|
||||
// GetDays renders HTML list of previous days' entries
|
||||
func GetDays(w http.ResponseWriter, r *http.Request) {
|
||||
GetEntries(w, r, TranslatableText("title.days"), "", "day", func(files []string) []Entry {
|
||||
description := template.HTML(
|
||||
"<a href=\"#footer\">" + TranslatableText("prompt.days") + "</a>")
|
||||
GetEntries(w, r, TranslatableText("title.days"), description, "day", func(files []string) []Entry {
|
||||
var filesFormatted []Entry
|
||||
for i := range files {
|
||||
v := files[len(files)-1-i] // This is suboptimal, but reverse order is better here
|
||||
|
@ -152,13 +170,6 @@ func GetEntry(w http.ResponseWriter, r *http.Request, title string, filename str
|
|||
}
|
||||
}
|
||||
|
||||
files := []string{"./pages/base.html"}
|
||||
if editable {
|
||||
files = append(files, "./pages/edit.html")
|
||||
} else {
|
||||
files = append(files, "./pages/entry.html")
|
||||
}
|
||||
|
||||
if editable {
|
||||
err = editTemplate.ExecuteTemplate(w, "base", Entry{Title: title, Content: string(entry)})
|
||||
} else {
|
||||
|
|
4
serve.go
4
serve.go
|
@ -51,8 +51,8 @@ func Serve() {
|
|||
r.Mount("/api", apiRouter)
|
||||
|
||||
// Static files
|
||||
fs := http.FileServer(http.Dir("public"))
|
||||
r.Handle("/public/*", http.StripPrefix("/public/", fs))
|
||||
fs := http.FileServer(http.FS(Public))
|
||||
r.Handle("/public/*", fs)
|
||||
|
||||
slog.Info("🌺 Website working", "port", Cfg.Port)
|
||||
slog.Debug("Debug mode enabled")
|
||||
|
|
Loading…
Reference in a new issue