Compare commits

..

No commits in common. "master" and "v1.1.3" have entirely different histories.

20 changed files with 78 additions and 170 deletions

View file

@ -1,45 +1,25 @@
# Changelog
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
* Fixed HTML `lang` tag
* Theme CSS link is now only present if non-default is set
* Improved template consistency (backend)
## v1.1.3
This release mostly consists of backend improvements
* List items no longer replace hyphens with spaces for consistency
* Telegram message for SCRAM is now translatable
* Ensured HTML escape in list descriptions
* Refactored many methods, improved comments
## v1.1.2
This release contains a few bug fixes
* Real IPs are now logged (By Rithas K.)
* CSS now has `box-sizing: border-box` to fix textarea in some cases (By Rithas K.)
* Done some minor code housekeeping
## 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.
## v1.1.0
* 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
* Fixed CSS `margin` and `text-align` inherited from my website
## 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
@ -53,20 +33,16 @@ This release includes several **breaking** changes
* Comic Sans MS for *everything*
* sorry
## v0.6.1
* Fixed date display when using `Local` timezone
## v0.6.0
* Replaced config reload with edit in info (api method still available, config reloads on save)
* Bug fixes
* Filenames are now sanitized when writing files
* "Tomorrow" in days list is now also displayed if Timezone is changed and grace period is off
* Frontend date display now uses configured timezone
### v0.6.1
* Fixed date display when using `Local` timezone
## v0.5.0
* Added a JS prompt to create new note
* "Sanitization" for this method is basic and assumes a well-meaning user
* Old instructions appear if JS is disabled
@ -75,7 +51,6 @@ This release includes several **breaking** changes
* Config reload now sets removed values to defaults
## v0.4.0
* Customisation changes
* Added `title` option to config
* Controls the text in the header, "🌺 Hibiscus.txt" by default
@ -86,20 +61,17 @@ This release includes several **breaking** changes
* Spaces in config options are now supported (basically just for `title`)
## v0.3.0
* Added themes
* 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)
* Current pre-made themes are `default`, `gruvbox` and `high-contrast`
## v0.2.0
* Added config reload
* Can be reloaded in info page
* Can be reloaded with new `reload` api method (be aware of the redirect if referer is present)
## 7 May 2024 - v0.1.0
* Began move towards [semantic versioning](https://semver.org/)
* Current version is now 0.1.0
* Added `version` api method
@ -114,7 +86,6 @@ This release includes several **breaking** changes
* Fixed export function failing
## 6 May 2024
* 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
* Made changes to date display on frontend
@ -124,7 +95,6 @@ This release includes several **breaking** changes
But I think it's fine.
## 5 May 2024
* Added this changelog
* Added grace period (as per suggestions)
* 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.
## Features:
* 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 easily export the files in a `.zip` archive for backups
* Everything is plain(text) and simple.
No databases, encryption, OAuth, or anything fancy. Even the password is plainte- *wait is this a feature?*
* [Docker support](#docker-deployment)
* Everything is plain(text) and simple. 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)
* Optional Telegram notifications for failed login attempts
## Technical details
[CHANGELOG.md](./CHANGELOG.md) provides a good overview of updates, and [TODO.md](./TODO.md) file shows my plans for the future.
[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.
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.
This repository is [self-hosted by me](https://git.a71.su/Andrew71/hibiscus),
but [mirrored to GitHub](https://github.com/Andrew-71/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.
### Data format:
```
data
+-- day
@ -44,12 +39,11 @@ config
Deleting notes is done by clearing contents and clicking "Save" - the app deletes empty files when saving.
### Config options:
Below are the available configuration options and their defaults.
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.
Below are available configuration options and their defaults.
The settings are defined as newline separated key=value pairs in config.txt.
If you do not provide an option in your config, it will be using the default.
Please don't include the bash-style "comments" in your actual config,
they are provided purely for demonstration and **will break the config if present**.
they are provided purely for demonstration only and **will break the config if present**.
```
username=admin # Your username
password=admin # Your password
@ -57,7 +51,7 @@ 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. 3h26m - files will change at 3:26am
language=en # ISO-639 language code (available - en, ru)
theme="" # Picked theme (available - default (if left empty), high-contrast, lavender, gruvbox, sans)
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
@ -71,14 +65,12 @@ tg_topic=message_thread_id
```
### Docker deployment:
The Docker images are hosted via GitHub over at `ghcr.io/andrew-71/hibiscus:<tag>`,
built from the [Dockerfile](./Dockerfile).
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.*
### Executable flags
If you decide to use plain executable instead of docker, it supports the following flags:
```
-config string
@ -94,7 +86,6 @@ If you decide to use plain executable instead of docker, it supports the followi
```
### API methods
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

11
TODO.md
View file

@ -1,18 +1,7 @@
# TODO
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)
* Auth improvement so it DOESN'T ASK ME FOR PASSWORD EVERY DAY UGH XD
## Nice to have
* Forward/backward buttons for days
* Changelog included
* Refactor code
* API field for version
* Notifications late in the day
* man page, maybe try packaging for fun
* store passwords as hash
## Brainstorming
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()}
updatedLogins := []failedLogin{attempt}
for _, attempt := range failedLogins {
if 100 > time.Since(attempt.Timestamp).Seconds() {
if 100 > time.Now().Sub(attempt.Timestamp).Abs().Seconds() {
updatedLogins = append(updatedLogins, attempt)
}
}

View file

@ -41,7 +41,7 @@ var DefaultConfig = Config{
Timezone: time.Local,
GraceTime: 0,
Language: "en",
Theme: "",
Theme: "default",
Title: "🌺 Hibiscus.txt",
LogToFile: false,
LogFile: "config/log.txt",
@ -120,7 +120,13 @@ func (c *Config) Reload() error {
}
}
case "bool":
fieldElem.SetBool(v == "true")
{
if v == "true" {
fieldElem.SetBool(true)
} else {
fieldElem.SetBool(false)
}
}
case "location":
timezone = v
case "duration":
@ -173,7 +179,7 @@ func ConfigReloadApi(w http.ResponseWriter, r *http.Request) {
HandleWrite(w.Write([]byte(err.Error())))
}
if r.Referer() != "" {
http.Redirect(w, r, r.Header.Get("Referer"), http.StatusFound)
http.Redirect(w, r, r.Header.Get("Referer"), 302)
return
}
w.WriteHeader(http.StatusOK)

2
go.mod
View file

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

View file

@ -1,6 +1,4 @@
{
"lang": "en-UK",
"title.today": "Your day so far",
"title.days": "Previous days",
"title.notes": "Notes",
@ -27,9 +25,5 @@
"info.config": "Edit config",
"info.telegram.auth_fail": "Failed auth attempt in Hibiscus.txt",
"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?"
"info.telegram.scram": "Hibiscus SCRAM triggered, shutting down"
}

View file

@ -1,6 +1,4 @@
{
"lang": "ru",
"title.today": "Сегодняшний день",
"title.days": "Предыдущие дни",
"title.notes": "Заметки",
@ -13,7 +11,7 @@
"link.info": "системная информация",
"time.date": "Сегодня",
"time.grace": "редактируется вчерашний день",
"time.grace": "льготный период",
"button.save": "Сохранить",
"button.notes": "Новая заметка",
"prompt.notes": "Название заметки",
@ -27,9 +25,5 @@
"info.config": "Редактировать конфиг",
"info.telegram_notification": "Неверная попытка авторизации в Hibiscus.txt",
"info.telegram.scram": "Активирована функция SCRAM в Hibiscus.txt, сервер выключается",
"error.404": "Страница, которую вы ищете, не существует",
"error.500": "Что-то сломалось",
"error.prompt": "На главную?"
"info.telegram.scram": "Активирована функция SCRAM в Hibiscus.txt, сервер выключается"
}

View file

@ -15,7 +15,7 @@ type AppInfo struct {
// Info contains app information.
var Info = AppInfo{
Version: "1.1.4",
Version: "1.1.3",
SourceLink: "https://git.a71.su/Andrew71/hibiscus",
}

View file

@ -5,7 +5,7 @@
</header>
{{end}}
{{- define "base" -}}
{{define "base"}}
<!DOCTYPE html>
<html lang="{{ translatableText "lang" }}">
<head>
@ -14,16 +14,16 @@
<link rel="manifest" href="/public/manifest.json" />
<link rel="icon" type="image/x-icon" href="/public/favicon.ico">
<link rel="stylesheet" href="/public/main.css">
{{- if config.Theme -}}<link rel="stylesheet" href="/public/themes/{{ config.Theme }}.css">{{ end }}
<link rel="stylesheet" href="/public/themes/{{ config.Theme }}.css">
<script src="/public/main.js"></script>
<title>Hibiscus.txt</title>
</head>
<body>
{{- template "header" . -}}
{{template "header" .}}
<main>
{{- template "main" . -}}
{{template "main" .}}
</main>
{{- template "footer" . -}}
{{template "footer" .}}
<script defer>
const langName="{{ config.Language }}";
const timeZone="{{ config.Timezone }}";

View file

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

View file

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

View file

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

View file

@ -0,0 +1 @@
/* Default theme is defined in main.css */

View file

@ -3,6 +3,7 @@ package main
import (
"embed"
"errors"
"github.com/go-chi/chi/v5"
"html/template"
"log/slog"
"net/http"
@ -10,8 +11,6 @@ import (
"os"
"strings"
"time"
"github.com/go-chi/chi/v5"
)
type EntryList struct {
@ -56,32 +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 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.
func NotFound(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404)
err := template404.Execute(w, nil)
if err != nil {
slog.Error("error rendering error 404 page", "error", err)
InternalError(w, r)
return
HandleWrite(w.Write(EmbeddedPage("pages/error/404.html")))
}
}
var template500 = template.Must(template.New("500").Funcs(templateFuncs).ParseFS(Pages, "pages/error/500.html"))
// InternalError returns a user-friendly 500 error page.
func InternalError(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
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
}
HandleWrite(w.Write(EmbeddedPage("pages/error/500.html")))
}
// GetEntries handles showing a list.
@ -179,7 +162,7 @@ func PostEntry(filename string, w http.ResponseWriter, r *http.Request) {
slog.Error("error saving file", "error", err, "file", filename)
}
if r.Referer() != "" {
http.Redirect(w, r, r.Header.Get("Referer"), http.StatusFound)
http.Redirect(w, r, r.Header.Get("Referer"), 302)
return
}
}
@ -193,7 +176,7 @@ func GetDay(w http.ResponseWriter, r *http.Request) {
return
}
if dayString == TodayDate() { // Today can still be edited
http.Redirect(w, r, "/", http.StatusFound)
http.Redirect(w, r, "/", 302)
return
}