Compare commits

..

No commits in common. "eb3c1fb32a1cda83419b04b688a2eabf73c6af26" and "8ae76cc8e886806e9dcc030afa056f129c5588d3" have entirely different histories.

13 changed files with 46 additions and 123 deletions

View file

@ -1,45 +1,30 @@
# Changelog # Changelog
This file keeps track of changes in a human-readable fashion This file keeps track of changes in a human-readable fashion
## Upcoming
These changes were not yet released
* Adjusted default theme
* Error pages are now translated
## v1.1.4 ## v1.1.4
* Fixed HTML `lang` tag * Fixed HTML `lang` tag
* Theme CSS link is now only present if non-default is set * Theme CSS link is now only present if non-default is set
* Improved template consistency (backend) * Improved template consistency (backend)
## v1.1.3 ## v1.1.3
This release mostly consists of backend improvements This release mostly consists of backend improvements
* List items no longer replace hyphens with spaces for consistency * List items no longer replace hyphens with spaces for consistency
* Telegram message for SCRAM is now translatable * Telegram message for SCRAM is now translatable
* Ensured HTML escape in list descriptions * Ensured HTML escape in list descriptions
* Refactored many methods, improved comments * Refactored many methods, improved comments
## v1.1.2 ## v1.1.2
This release contains a few bug fixes This release contains a few bug fixes
* Real IPs are now logged (By Rithas K.) * Real IPs are now logged (By Rithas K.)
* CSS now has `box-sizing: border-box` to fix textarea in some cases (By Rithas K.) * CSS now has `box-sizing: border-box` to fix textarea in some cases (By Rithas K.)
* Done some minor code housekeeping * Done some minor code housekeeping
## v1.1.1 ## v1.1.1
This release is mostly a technicality, with a move over to GitHub (`ghcr.io/andrew-71/hibiscus`) for packages due to DockerHub's prior anti-Russian actions making old "CI/CD" unsustainable. This release is mostly a technicality, with a move over to GitHub (`ghcr.io/andrew-71/hibiscus`) for packages due to DockerHub's prior anti-Russian actions making old "CI/CD" unsustainable.
## v1.1.0 ## v1.1.0
* You can now specify the Telegram *topic* to send notification to via `tg_topic` config key (By Rithas K.) * You can now specify the Telegram *topic* to send notification to via `tg_topic` config key (By Rithas K.)
* The Telegram message is now partially translated * The Telegram message is now partially translated
* Fixed CSS `margin` and `text-align` inherited from my website * Fixed CSS `margin` and `text-align` inherited from my website
## v1.0.0 ## v1.0.0
This release includes several **breaking** changes This release includes several **breaking** changes
* Made a new favicon * Made a new favicon
* English is now used as a fallback language, making incomplete translations somewhat usable * English is now used as a fallback language, making incomplete translations somewhat usable
@ -53,20 +38,16 @@ This release includes several **breaking** changes
* Comic Sans MS for *everything* * Comic Sans MS for *everything*
* sorry * sorry
## v0.6.1
* Fixed date display when using `Local` timezone
## v0.6.0 ## v0.6.0
* Replaced config reload with edit in info (api method still available, config reloads on save) * Replaced config reload with edit in info (api method still available, config reloads on save)
* Bug fixes * Bug fixes
* Filenames are now sanitized when writing files * Filenames are now sanitized when writing files
* "Tomorrow" in days list is now also displayed if Timezone is changed and grace period is off * "Tomorrow" in days list is now also displayed if Timezone is changed and grace period is off
* Frontend date display now uses configured timezone * Frontend date display now uses configured timezone
### v0.6.1
* Fixed date display when using `Local` timezone
## v0.5.0 ## v0.5.0
* Added a JS prompt to create new note * Added a JS prompt to create new note
* "Sanitization" for this method is basic and assumes a well-meaning user * "Sanitization" for this method is basic and assumes a well-meaning user
* Old instructions appear if JS is disabled * Old instructions appear if JS is disabled
@ -75,7 +56,6 @@ This release includes several **breaking** changes
* Config reload now sets removed values to defaults * Config reload now sets removed values to defaults
## v0.4.0 ## v0.4.0
* Customisation changes * Customisation changes
* Added `title` option to config * Added `title` option to config
* Controls the text in the header, "🌺 Hibiscus.txt" by default * Controls the text in the header, "🌺 Hibiscus.txt" by default
@ -86,20 +66,17 @@ This release includes several **breaking** changes
* Spaces in config options are now supported (basically just for `title`) * Spaces in config options are now supported (basically just for `title`)
## v0.3.0 ## v0.3.0
* Added themes * Added themes
* Picked theme is set by `theme` key in config. Default is ...`default` * Picked theme is set by `theme` key in config. Default is ...`default`
* Themes are defined in `/public/themes/<name>.css` and modify colours (or, theoretically, do more) * Themes are defined in `/public/themes/<name>.css` and modify colours (or, theoretically, do more)
* Current pre-made themes are `default`, `gruvbox` and `high-contrast` * Current pre-made themes are `default`, `gruvbox` and `high-contrast`
## v0.2.0 ## v0.2.0
* Added config reload * Added config reload
* Can be reloaded in info page * Can be reloaded in info page
* Can be reloaded with new `reload` api method (be aware of the redirect if referer is present) * Can be reloaded with new `reload` api method (be aware of the redirect if referer is present)
## 7 May 2024 - v0.1.0 ## 7 May 2024 - v0.1.0
* Began move towards [semantic versioning](https://semver.org/) * Began move towards [semantic versioning](https://semver.org/)
* Current version is now 0.1.0 * Current version is now 0.1.0
* Added `version` api method * Added `version` api method
@ -114,7 +91,6 @@ This release includes several **breaking** changes
* Fixed export function failing * Fixed export function failing
## 6 May 2024 ## 6 May 2024
* Grace period is now non-inclusive (so `4h` means the switch will happen right at `4:00`, not `4:01`) * Grace period is now non-inclusive (so `4h` means the switch will happen right at `4:00`, not `4:01`)
* Added API method to check if grace period is active * Added API method to check if grace period is active
* Made changes to date display on frontend * Made changes to date display on frontend
@ -124,7 +100,6 @@ This release includes several **breaking** changes
But I think it's fine. But I think it's fine.
## 5 May 2024 ## 5 May 2024
* Added this changelog * Added this changelog
* Added grace period (as per suggestions) * Added grace period (as per suggestions)
* Set in config like `grace_period=3h26m` (via Go's `time.ParseDuration`) * Set in config like `grace_period=3h26m` (via Go's `time.ParseDuration`)

View file

@ -1,8 +0,0 @@
build:
go build
run:
go build & ./hibiscus-txt
dev:
go build & ./hibiscus-txt --config config/dev-config.txt

View file

@ -6,27 +6,22 @@ This project is *very* opinionated and minimal, and is designed primarily for my
As a result, I can't guarantee that it's either secure or stable. As a result, I can't guarantee that it's either secure or stable.
## Features: ## Features:
* Each day, you get a new text file. You have until the end of that very day to finalise it. * Each day, you get a new text file. You have until the end of that very day to finalise it.
* You can save named notes to document milestones, big events, or just a nice game you played this month * You can save named notes to document milestones, big events, or just a nice game you played this month
* You can easily export the files in a `.zip` archive for backups * You can easily export the files in a `.zip` archive for backups
* Everything is plain(text) and simple. * Everything is plain(text) and simple. No databases, encryption, OAuth, or anything fancy. Even the password is plainte- *wait is this a feature?*
No databases, encryption, OAuth, or anything fancy. Even the password is plainte- *wait is this a feature?* * [Docker support](#docker-deployment) (in fact, that's probably the best way to run this)
* [Docker support](#docker-deployment)
* Optional Telegram notifications for failed login attempts * Optional Telegram notifications for failed login attempts
## Technical details ## Technical details
[CHANGELOG.md](./CHANGELOG.md) provides a good overview of updates, and [TODO.md](./TODO.md) file shows what I will (or *may*) work on in the future.
[CHANGELOG.md](./CHANGELOG.md) provides a good overview of updates, and [TODO.md](./TODO.md) file shows my plans for the future.
You can read a relevant entry in my blog [here](https://a71.su/notes/hibiscus/). You can read a relevant entry in my blog [here](https://a71.su/notes/hibiscus/).
It provides some useful information and context for why this app exists in the first place. It provides some useful information and context for why this app exists in the first place.
This repository is [self-hosted by me](https://git.a71.su/Andrew71/hibiscus), This repository is [self-hosted by me](https://git.a71.su/Andrew71/hibiscus), but [mirrored to GitHub](https://github.com/Andrew-71/hibiscus) in case my server goes down.
but [mirrored to GitHub](https://github.com/Andrew-71/hibiscus).
### Data format: ### Data format:
``` ```
data data
+-- day +-- day
@ -44,7 +39,6 @@ config
Deleting notes is done by clearing contents and clicking "Save" - the app deletes empty files when saving. Deleting notes is done by clearing contents and clicking "Save" - the app deletes empty files when saving.
### Config options: ### Config options:
Below are the available configuration options and their defaults. Below are the available configuration options and their defaults.
The settings are defined as newline separated `key=value` pairs in the config file. The settings are defined as newline separated `key=value` pairs in the config file.
If you do not provide an option, the default will be used. If you do not provide an option, the default will be used.
@ -71,14 +65,12 @@ tg_topic=message_thread_id
``` ```
### Docker deployment: ### Docker deployment:
The Docker images are hosted via GitHub over at `ghcr.io/andrew-71/hibiscus:<tag>`, The Docker images are hosted via GitHub over at `ghcr.io/andrew-71/hibiscus:<tag>`,
built from the [Dockerfile](./Dockerfile). built from the [Dockerfile](./Dockerfile).
This repo contains the [compose.yml](./compose.yml) that I personally use. This repo contains the [compose.yml](./compose.yml) that I personally use.
*Note: an extremely outdated self-hosted [package](https://git.a71.su/Andrew71/hibiscus/packages) will be provided for some time.* *Note: an extremely outdated self-hosted [package](https://git.a71.su/Andrew71/hibiscus/packages) will be provided for some time.*
### Executable flags ### Executable flags
If you decide to use plain executable instead of docker, it supports the following flags: If you decide to use plain executable instead of docker, it supports the following flags:
``` ```
-config string -config string
@ -94,7 +86,6 @@ If you decide to use plain executable instead of docker, it supports the followi
``` ```
### API methods ### API methods
You can access the API at `/api/<method>`. It is protected by same HTTP Basic Auth as "normal" routes. You can access the API at `/api/<method>`. It is protected by same HTTP Basic Auth as "normal" routes.
``` ```
GET /today - get file contents for today GET /today - get file contents for today

View file

@ -1,15 +1,8 @@
# TODO # TODO
List of things to add to this project List of things to add to this project
## Urgent (1.1.5-2.0.0)
* `style.css` in config instead of theme (provide themes as examples in repo)
* man page, maybe try packaging for fun
* Auth improvement so it DOESN'T ASK ME FOR PASSWORD EVERY DAY UGH XD * Auth improvement so it DOESN'T ASK ME FOR PASSWORD EVERY DAY UGH XD
## Nice to have
* Forward/backward buttons for days * Forward/backward buttons for days
* Changelog included
* Refactor code
## Brainstorming ## Brainstorming
Don't expect any of this, these are ideas floating inside my head Don't expect any of this, these are ideas floating inside my head

View file

@ -27,7 +27,7 @@ func NoteLoginFail(username string, password string, r *http.Request) {
attempt := failedLogin{username, password, time.Now()} attempt := failedLogin{username, password, time.Now()}
updatedLogins := []failedLogin{attempt} updatedLogins := []failedLogin{attempt}
for _, attempt := range failedLogins { for _, attempt := range failedLogins {
if 100 > time.Since(attempt.Timestamp).Seconds() { if 100 > time.Now().Sub(attempt.Timestamp).Abs().Seconds() {
updatedLogins = append(updatedLogins, attempt) updatedLogins = append(updatedLogins, attempt)
} }
} }

View file

@ -120,7 +120,13 @@ func (c *Config) Reload() error {
} }
} }
case "bool": case "bool":
fieldElem.SetBool(v == "true") {
if v == "true" {
fieldElem.SetBool(true)
} else {
fieldElem.SetBool(false)
}
}
case "location": case "location":
timezone = v timezone = v
case "duration": case "duration":
@ -173,7 +179,7 @@ func ConfigReloadApi(w http.ResponseWriter, r *http.Request) {
HandleWrite(w.Write([]byte(err.Error()))) HandleWrite(w.Write([]byte(err.Error())))
} }
if r.Referer() != "" { if r.Referer() != "" {
http.Redirect(w, r, r.Header.Get("Referer"), http.StatusFound) http.Redirect(w, r, r.Header.Get("Referer"), 302)
return return
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)

2
go.mod
View file

@ -1,4 +1,4 @@
module hibiscus-txt module hibiscus
go 1.22 go 1.22

View file

@ -27,9 +27,5 @@
"info.config": "Edit config", "info.config": "Edit config",
"info.telegram.auth_fail": "Failed auth attempt in Hibiscus.txt", "info.telegram.auth_fail": "Failed auth attempt in Hibiscus.txt",
"info.telegram.scram": "Hibiscus SCRAM triggered, shutting down", "info.telegram.scram": "Hibiscus SCRAM triggered, shutting down"
"error.404": "The page you were looking for doesn't exist",
"error.500": "It's probably not your fault, but something broke",
"error.prompt": "Go home?"
} }

View file

@ -27,9 +27,5 @@
"info.config": "Редактировать конфиг", "info.config": "Редактировать конфиг",
"info.telegram_notification": "Неверная попытка авторизации в Hibiscus.txt", "info.telegram_notification": "Неверная попытка авторизации в Hibiscus.txt",
"info.telegram.scram": "Активирована функция SCRAM в Hibiscus.txt, сервер выключается", "info.telegram.scram": "Активирована функция SCRAM в Hibiscus.txt, сервер выключается"
"error.404": "Страница, которую вы ищете, не существует",
"error.500": "Что-то сломалось",
"error.prompt": "На главную?"
} }

View file

@ -1,4 +1,3 @@
{{- define "404" -}}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -11,9 +10,8 @@
<body> <body>
<main> <main>
<h1>Error 404 - Not Found</h1> <h1>Error 404 - Not Found</h1>
<p>{{ translatableText "error.404" }}</p> <p>The page you were looking for doesn't exist or was moved</p>
<p><a href="/">{{ translatableText "error.prompt" }}</a></p> <h3><a href="/">Go home?</a></h3>
</main> </main>
</body> </body>
</html> </html>
{{ end }}

View file

@ -1,4 +1,3 @@
{{- define "500" -}}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -11,9 +10,8 @@
<body> <body>
<main> <main>
<h1>Error 500 - Internal Server Error</h1> <h1>Error 500 - Internal Server Error</h1>
<p>{{ translatableText "error.500" }}</p> <p>It's probably not your fault, but something broke</p>
<p><a href="/">{{ translatableText "error.prompt" }}</a></p> <h3><a href="/">Go home?</a></h3>
</main> </main>
</body> </body>
</html> </html>
{{ end }}

View file

@ -1,57 +1,49 @@
/* Default theme */ /* Default theme */
:root { :root {
/* Light theme */ /* Light theme */
--text-light: #2b2a2a; --text-light: #454545;
--bg-light: #f4edd7; --bg-light: #f5f0e1;
--clickable-light: #ed3e3b; --clickable-light: #f85552;
--clickable-hover-light: #e55552; --clickable-hover-light: #e66868;
--clickable-label-light: #f4edd7; --clickable-label-light: #f5f2ee;
--text-hover-light: #656565; --text-hover-light: #656565;
--textarea-bg-light: #f9f5e4; --textarea-bg-light: #f5f2ee;
--textarea-border-light: #c3c3c2; --textarea-border-light: #454545;
/* Dark theme */ /* Dark theme */
--text-dark: #f5f0e1; --text-dark: #f5f0e1;
--bg-dark: #1b1916; --bg-dark: #2c2825;
--clickable-dark: #ed3e3b; --clickable-dark: #f85552;
--clickable-hover-dark: #ae3836; --clickable-hover-dark: #e66868;
--clickable-label-dark: #f5f2ee; --clickable-label-dark: #f5f2ee;
--text-hover-dark: #a9a8a4; --text-hover-dark: #656565;
--textarea-bg-dark: #201d1b; /* 252020 f5f0e1 */ --textarea-bg-dark: #383030;
--textarea-border-dark: #2c2727; --textarea-border-dark: #454545;
} }
* { box-sizing: border-box; } * { box-sizing: border-box; }
body { body {
color: var(--text-light); color: var(--text-light);
background-color: var(--bg-light); background-color: var(--bg-light);
font-size: 18px;
margin: auto auto; margin: auto auto;
max-width: 640px; max-width: 640px;
padding: 15px; padding: 1em;
line-height: 1.4; line-height: 1.4;
font-family: serif; font-family: serif;
min-height: 85vh; min-height: 85vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
h1,h2,h3,h4,h5,h6 { line-height: 1.2 }
a, a:visited { color: var(--clickable-light); } a, a:visited { color: var(--clickable-light); }
a:hover, a:visited:hover { color: var(--clickable-hover-light); } a:hover, a:visited:hover { color: var(--clickable-hover-light); }
a.no-accent, a.no-accent:visited { color: var(--text-light); } a.no-accent, a.no-accent:visited { color: var(--text-light); }
a.no-accent:hover, a.no-accent:visited:hover { color: var(--text-hover-light); } a.no-accent:hover, a.no-accent:visited:hover { color: var(--text-hover-light); }
ul:not(li ul), ol:not(li ol){ h2 { margin-bottom:12px; }
margin-left: 0;
padding-left: 0;
list-style-position: inside;
}
.list-title { margin-bottom: 0} .list-title { margin-bottom: 0}
.list-desc { margin-top: 0 } .list-desc { margin-top: 0 }
@ -63,7 +55,7 @@ textarea, input {
resize: vertical; resize: vertical;
outline: 0; outline: 0;
box-shadow: none; box-shadow: none;
border: 2px solid var(--textarea-border-light); border: 0.0625em solid var(--textarea-border-light);
margin-bottom: 1em; margin-bottom: 1em;
font-size: 18px; font-size: 18px;
} }

View file

@ -55,30 +55,16 @@ var editTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFS(P
var viewTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFS(Pages, "pages/base.html", "pages/entry.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")) var listTemplate = template.Must(template.New("").Funcs(templateFuncs).ParseFS(Pages, "pages/base.html", "pages/list.html"))
var template404 = template.Must(template.New("404").Funcs(templateFuncs).ParseFS(Pages, "pages/error/404.html"))
// NotFound returns a user-friendly 404 error page. // NotFound returns a user-friendly 404 error page.
func NotFound(w http.ResponseWriter, r *http.Request) { func NotFound(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404) w.WriteHeader(404)
HandleWrite(w.Write(EmbeddedPage("pages/error/404.html")))
err := template404.Execute(w, nil)
if err != nil {
slog.Error("error rendering error 404 page", "error", err)
InternalError(w, r)
return
}
} }
var template500 = template.Must(template.New("500").Funcs(templateFuncs).ParseFS(Pages, "pages/error/500.html"))
// InternalError returns a user-friendly 500 error page. // InternalError returns a user-friendly 500 error page.
func InternalError(w http.ResponseWriter, r *http.Request) { func InternalError(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500) w.WriteHeader(500)
HandleWrite(w.Write(EmbeddedPage("pages/error/500.html")))
err := template500.Execute(w, nil)
if err != nil { // Well this is awkward
slog.Error("error rendering error 500 page", "error", err)
HandleWrite(w.Write([]byte("500. Something went *very* wrong.")))
return
}
} }
// GetEntries handles showing a list. // GetEntries handles showing a list.
@ -176,7 +162,7 @@ func PostEntry(filename string, w http.ResponseWriter, r *http.Request) {
slog.Error("error saving file", "error", err, "file", filename) slog.Error("error saving file", "error", err, "file", filename)
} }
if r.Referer() != "" { if r.Referer() != "" {
http.Redirect(w, r, r.Header.Get("Referer"), http.StatusFound) http.Redirect(w, r, r.Header.Get("Referer"), 302)
return return
} }
} }
@ -190,7 +176,7 @@ func GetDay(w http.ResponseWriter, r *http.Request) {
return return
} }
if dayString == TodayDate() { // Today can still be edited if dayString == TodayDate() { // Today can still be edited
http.Redirect(w, r, "/", http.StatusFound) http.Redirect(w, r, "/", 302)
return return
} }