aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md3
-rw-r--r--backend/config.go5
-rw-r--r--backend/data.go6
-rw-r--r--backend/home.go1
-rw-r--r--backend/parser.go22
-rw-r--r--backend/router.go11
-rw-r--r--backend/section.go1
-rw-r--r--backend/templates/base.html7
-rw-r--r--bun.lock21
-rw-r--r--frontend/index.ts47
-rw-r--r--markdown/ast_external.go6
-rw-r--r--markdown/eval.go6
-rw-r--r--package.json10
13 files changed, 48 insertions, 98 deletions
diff --git a/README.md b/README.md
index cc99a04..ace50dd 100644
--- a/README.md
+++ b/README.md
@@ -13,9 +13,8 @@ It aims to be simple, minimalist, brutalist, indie, and personnal.
Backend written in modern Go.
-Light CSS, light JS, runs everywhere.
+Light CSS, runs everywhere.
SSR first.
-Optional HTMX to fluidify the navigation.
Content written in markdown.
diff --git a/backend/config.go b/backend/config.go
index cd24592..44b2033 100644
--- a/backend/config.go
+++ b/backend/config.go
@@ -5,7 +5,6 @@ import (
"log/slog"
"os"
- "git.anhgelus.world/anhgelus/small-web/markdown"
"github.com/pelletier/go-toml/v2"
)
@@ -14,8 +13,8 @@ type Link struct {
URL string `toml:"url"`
}
-func (l *Link) Render() template.HTML {
- return markdown.RenderLink(l.Name, l.URL)
+func (l *Link) Render(url string) template.HTML {
+ return renderLink(l.Name, l.URL, url)
}
type Logo struct {
diff --git a/backend/data.go b/backend/data.go
index 1b40e73..5bc5391 100644
--- a/backend/data.go
+++ b/backend/data.go
@@ -11,7 +11,6 @@ import (
"log/slog"
"math/rand"
"net/http"
- "net/url"
"regexp"
"strings"
txt "text/template"
@@ -103,11 +102,6 @@ func (d *data) handleGeneric(w http.ResponseWriter, r *http.Request, name string
panic(err)
}
exec := "base.html"
- if r.Context().Value(isUpdateKey).(bool) {
- exec = "body"
- w.Header().Set("Updated-Title", url.QueryEscape(d.Title()))
- w.Header().Set("Updated-Quote", url.QueryEscape(d.Quote))
- }
if custom == nil {
err = t.ExecuteTemplate(w, exec, d)
} else {
diff --git a/backend/home.go b/backend/home.go
index be01b9d..f824a69 100644
--- a/backend/home.go
+++ b/backend/home.go
@@ -83,6 +83,7 @@ func handleGenericRoot(w http.ResponseWriter, r *http.Request, name string) {
}
panic(err)
}
+ d.URL = "/" + name
d.Content, ok = parse(b, new(EntryInfo), d.data)
if !ok {
w.WriteHeader(http.StatusInternalServerError)
diff --git a/backend/parser.go b/backend/parser.go
index c7d6445..b8e730b 100644
--- a/backend/parser.go
+++ b/backend/parser.go
@@ -18,6 +18,23 @@ type EntryInfo struct {
PubLocalDate toml.LocalDate `toml:"publication_date"`
}
+func renderLinkFunc(url string) func(string, string) template.HTML {
+ return func(content, href string) template.HTML {
+ b := "<a"
+ if href == url || (href != "/" && url != "/" && strings.HasPrefix(url, href)) {
+ b += ` class="target"`
+ }
+ if markdown.ExternalLink.MatchString(href) {
+ b += ` target="_blank"`
+ }
+ return template.HTML(fmt.Sprintf(`%s href="%s">%s</a>`, b, href, content))
+ }
+}
+
+func renderLink(content, href, url string) template.HTML {
+ return renderLinkFunc(url)(content, href)
+}
+
func parse(b []byte, info *EntryInfo, d *data) (template.HTML, bool) {
var dd string
splits := strings.SplitN(string(b), "---", 2)
@@ -31,7 +48,10 @@ func parse(b []byte, info *EntryInfo, d *data) (template.HTML, bool) {
} else {
dd = string(b)
}
- content, err := markdown.Parse(dd, &markdown.Option{ImageSource: getStatic})
+ opt := new(markdown.Option)
+ opt.ImageSource = getStatic
+ opt.RenderLink = renderLinkFunc(d.URL)
+ content, err := markdown.Parse(dd, opt)
var errMd *markdown.ParseError
errors.As(err, &errMd)
if errMd != nil {
diff --git a/backend/router.go b/backend/router.go
index 3e6a39a..3eede92 100644
--- a/backend/router.go
+++ b/backend/router.go
@@ -19,7 +19,6 @@ import (
const (
Version = "0.4.0"
configKey = "config"
- isUpdateKey = "is_update"
assetsFSKey = "assets_fs"
debugKey = "debug"
)
@@ -95,16 +94,6 @@ func NewRouter(debug bool, cfg *Config, assets fs.FS) *chi.Mux {
next.ServeHTTP(w, r.WithContext(ctx))
})
})
- r.Use(func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- val := false
- if r.Header.Get("HX-Request") == "true" {
- val = true
- }
- ctx := context.WithValue(r.Context(), isUpdateKey, val)
- next.ServeHTTP(w, r.WithContext(ctx))
- })
- })
return r
}
diff --git a/backend/section.go b/backend/section.go
index e6e318a..ff2cf43 100644
--- a/backend/section.go
+++ b/backend/section.go
@@ -216,6 +216,7 @@ func (s *Section) parse(d *sectionData, mu *sync.Mutex, path, slug string) bool
panic(err)
}
var ok bool
+ d.data.URL = fmt.Sprintf("/%s/%s", s.URI, slug)
d.Content, ok = parse(b, &d.EntryInfo, d.data)
if !ok {
return false
diff --git a/backend/templates/base.html b/backend/templates/base.html
index 0089587..20abcd0 100644
--- a/backend/templates/base.html
+++ b/backend/templates/base.html
@@ -28,10 +28,11 @@
<meta name="twitter:description" content="{{ .PageDescription }}" />
<meta name="twitter:image" content="{{ fullStatic .Image }}" />
</head>
- <body hx-push-url="true">
+ <body>
<header>
<img src="{{ static .Logo.Header }}" alt="Logo" />
- <nav>{{ range .Links }}{{ .Render }}{{end}}</nav>
+ {{ $url := .URL }}
+ <nav>{{ range .Links }}{{ .Render $url }}{{end}}</nav>
</header>
{{ template "body" . }}
<footer>
@@ -42,7 +43,5 @@
<a href="https://git.anhgelus.world/anhgelus/small-web" target="_blank" rel="noreferrer">code source</a>.
</p>
</footer>
- {{ $script := asset "index.js" }}
- <script src="{{ $script.Src }}" integrity="{{ $script.Checksum }}" defer></script>
</body>
</html>
diff --git a/bun.lock b/bun.lock
index 93505bf..e8fcdfd 100644
--- a/bun.lock
+++ b/bun.lock
@@ -4,17 +4,16 @@
"": {
"name": "small-web",
"dependencies": {
- "htmx.org": "2.0.7",
"reset-css": "^5.0.2",
- "sass": "^1.93.2",
+ "sass": "^1.95.1",
"scss": "^0.2.4",
},
"devDependencies": {
"@types/bun": "latest",
- "prettier": "^3.6.2",
+ "prettier": "^3.7.4",
},
"peerDependencies": {
- "typescript": "^5",
+ "typescript": "^5.9.3",
},
},
},
@@ -47,26 +46,20 @@
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
- "@types/bun": ["@types/bun@1.2.23", "", { "dependencies": { "bun-types": "1.2.23" } }, "sha512-le8ueOY5b6VKYf19xT3McVbXqLqmxzPXHsQT/q9JHgikJ2X22wyTW3g3ohz2ZMnp7dod6aduIiq8A14Xyimm0A=="],
+ "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
"@types/node": ["@types/node@24.6.2", "", { "dependencies": { "undici-types": "~7.13.0" } }, "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang=="],
- "@types/react": ["@types/react@19.2.0", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA=="],
-
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
- "bun-types": ["bun-types@1.2.23", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-R9f0hKAZXgFU3mlrA0YpE/fiDvwV0FT9rORApt2aQVWSuJDzZOyB5QLc0N/4HF57CS8IXJ6+L5E4W1bW6NS2Aw=="],
+ "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
- "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
-
"detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
- "htmx.org": ["htmx.org@2.0.7", "", {}, "sha512-YiJqF3U5KyO28VC5mPfehKJPF+n1Gni+cupK+D69TF0nm7wY6AXn3a4mPWIikfAXtl1u1F1+ZhSCS7KT8pVmqA=="],
-
"immutable": ["immutable@5.1.3", "", {}, "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
@@ -83,13 +76,13 @@
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
- "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
+ "prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"reset-css": ["reset-css@5.0.2", "", {}, "sha512-YtgUGSq5z5W0NPSjsBW7ys7rtWa8P8AiE7S6Fg3d1TQCPpAodgYyLuZYlU0AOsLtprk/fC9ormHN/0pAavVIDw=="],
- "sass": ["sass@1.93.2", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg=="],
+ "sass": ["sass@1.95.1", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-uPoDh5NIEZV4Dp5GBodkmNY9tSQfXY02pmCcUo+FR1P+x953HGkpw+vV28D4IqYB6f8webZtwoSaZaiPtpTeMg=="],
"scss": ["scss@0.2.4", "", { "dependencies": { "ometa": "0.2.2" } }, "sha512-4u8V87F+Q/upVhUmhPnB4C1R11xojkRkWjExL2v0CX2EXTg18VrKd+9JWoeyCp2VEMdSpJsyAvVU+rVjogh51A=="],
diff --git a/frontend/index.ts b/frontend/index.ts
deleted file mode 100644
index d3c974f..0000000
--- a/frontend/index.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import htmx from "htmx.org";
-
-htmx.config.historyRestoreAsHxRequest = false;
-htmx.config.includeIndicatorStyles = false;
-
-function setupAnchors() {
- document.querySelectorAll("a").forEach((e) => {
- // stuff related to external links are already handled in the backend
- if (!e.href.startsWith(window.location.origin) && /https?:\/\//.test(e.href)) return;
- // stuff related to RSS must not be processed by HTMX.
- if (e.href.endsWith("/rss/") || e.href.endsWith("/rss")) {
- e.target = "_blank";
- return;
- }
-
- if (e.href == window.location.href) e.classList.add("target");
- else e.classList.remove("target");
-
- if (e.hasAttribute("hx-trigger")) return;
-
- e.setAttribute("hx-get", e.href);
- e.setAttribute("hx-trigger", "click");
- e.setAttribute("hx-target", "#content");
- e.setAttribute("hx-swap", "outerHTML show:body:top");
- htmx.process(e);
- });
-}
-
-// updating history and window title
-document.addEventListener("htmx:afterSettle", (e) => {
- if (e.detail.xhr === undefined) return;
- const title = e.detail.xhr.getResponseHeader("Updated-Title");
- if (title?.length !== 0) document.title = decodeURIComponent(title).replaceAll("+", " ");
- const quote = e.detail.xhr.getResponseHeader("Updated-Quote");
- if (quote?.length !== 0)
- document.querySelector("#quote")!.innerHTML =
- "«&thinsp;" + decodeURIComponent(quote).replaceAll("+", " ") + "&thinsp;»";
- setupAnchors();
-});
-
-document.body.addEventListener("htmx:beforeSwap", function (e) {
- if (e.detail.xhr.status !== 404) return;
- e.detail.shouldSwap = true;
- e.detail.isError = false;
-});
-
-setupAnchors();
diff --git a/markdown/ast_external.go b/markdown/ast_external.go
index ee00eab..8adeea1 100644
--- a/markdown/ast_external.go
+++ b/markdown/ast_external.go
@@ -6,7 +6,7 @@ import (
"regexp"
)
-var externalLink = regexp.MustCompile(`https?://`)
+var ExternalLink = regexp.MustCompile(`https?://`)
type astLink struct {
content block
@@ -23,7 +23,7 @@ func (a *astLink) Eval(opt *Option) (template.HTML, *ParseError) {
if err != nil {
return "", err
}
- rr := RenderLink(string(content), string(href))
+ rr := opt.RenderLink(string(content), string(href))
if a.addSpace {
return " " + rr, nil
}
@@ -31,7 +31,7 @@ func (a *astLink) Eval(opt *Option) (template.HTML, *ParseError) {
}
func RenderLink(content, href string) template.HTML {
- if !externalLink.Match([]byte(href)) {
+ if !ExternalLink.Match([]byte(href)) {
return template.HTML(fmt.Sprintf(`<a href="%s">%s</a>`, href, content))
}
return template.HTML(fmt.Sprintf(`<a href="%s" target="_blank" rel="noreferer">%s</a>`, href, content))
diff --git a/markdown/eval.go b/markdown/eval.go
index 56bb989..376e577 100644
--- a/markdown/eval.go
+++ b/markdown/eval.go
@@ -3,7 +3,8 @@ package markdown
import "html/template"
type Option struct {
- ImageSource func(string) string
+ ImageSource func(source string) string
+ RenderLink func(content, href string) template.HTML
}
func Parse(s string, opt *Option) (template.HTML, *ParseError) {
@@ -18,6 +19,9 @@ func Parse(s string, opt *Option) (template.HTML, *ParseError) {
if opt.ImageSource == nil {
opt.ImageSource = func(s string) string { return s }
}
+ if opt.RenderLink == nil {
+ opt.RenderLink = RenderLink
+ }
return tree.Eval(opt)
}
diff --git a/package.json b/package.json
index 6946be4..d3b2fd7 100644
--- a/package.json
+++ b/package.json
@@ -6,21 +6,19 @@
"scripts": {
"build:sass": "sass --no-source-map --style=compressed frontend/scss/main.scss dist/styles.css",
"watch:sass": "sass --watch frontend/scss/main.scss dist/styles.css",
- "build:js": "bun build frontend/index.ts --outdir ./dist --minify",
- "build": "bun run build:sass && bun run build:js",
+ "build": "bun run build:sass",
"format": "prettier . --write"
},
"devDependencies": {
"@types/bun": "latest",
- "prettier": "^3.6.2"
+ "prettier": "^3.7.4"
},
"peerDependencies": {
- "typescript": "^5"
+ "typescript": "^5.9.3"
},
"dependencies": {
- "htmx.org": "2.0.7",
"reset-css": "^5.0.2",
- "sass": "^1.93.2",
+ "sass": "^1.95.1",
"scss": "^0.2.4"
}
}