From a783270b09af3d873c08a01d13f802018b69fb02 Mon Sep 17 00:00:00 2001 From: a73x Date: Sun, 29 Dec 2024 19:13:27 +0000 Subject: new markdown renderer since TOC has a title now and it can compact toc headers, we can use header 2 for everything use the built in goldmark extension for syntax highlighting --- Makefile | 4 - contrib/styles.go | 20 -- go.mod | 6 +- go.sum | 16 +- html/highlight.go | 79 -------- html/html.go | 61 +++++- pages/pages.go | 6 +- public/ethos.html | 53 ++--- public/index.html | 68 +++---- public/posts.html | 60 +++--- public/posts/2024-08-25-01.html | 80 ++++---- public/posts/2024-08-25-02.html | 150 ++++++-------- public/posts/2024-08-25-03.html | 52 +++-- public/posts/2024-08-26-01.html | 428 +++++++++++++++++++--------------------- public/posts/2024-08-31-01.html | 67 +++---- public/posts/2024-09-04-01.html | 152 ++++++-------- public/posts/2024-09-08-01.html | 250 +++++++++-------------- public/posts/2024-11-09-01.html | 36 +--- public/posts/2024-12-08-01.html | 82 ++++---- public/posts/2024-12-08-02.html | 39 ++-- public/posts/2024-12-28-01.html | 94 ++++----- public/static/styles.css | 43 ++-- public/static/syntax.css | 69 ------- templates/layouts/_default.html | 3 +- 24 files changed, 808 insertions(+), 1110 deletions(-) delete mode 100644 contrib/styles.go delete mode 100644 public/static/syntax.css diff --git a/Makefile b/Makefile index b092524..d659bd2 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,6 @@ build: serve: go run ./cmd/serve -.PHONY: css -css: - go run contrib/styles.go > public/static/syntax.css - .PHONY: image image: docker build . -t ${DOCKER_IMAGE} diff --git a/contrib/styles.go b/contrib/styles.go deleted file mode 100644 index 2b02b95..0000000 --- a/contrib/styles.go +++ /dev/null @@ -1,20 +0,0 @@ -// style.go: generate chroma css -// go run contrib/style.go > syntax.css -package main - -import ( - "os" - - "github.com/alecthomas/chroma/formatters/html" - "github.com/alecthomas/chroma/styles" -) - -func main() { - style := styles.Get("xcode") // Choose your preferred style - if style == nil { - style = styles.Fallback - } - - formatter := html.New(html.WithClasses(true), html.TabWidth(2)) - formatter.WriteCSS(os.Stdout, style) -} diff --git a/go.mod b/go.mod index 957ad30..b257e92 100644 --- a/go.mod +++ b/go.mod @@ -5,14 +5,18 @@ go 1.23 require ( github.com/adrg/frontmatter v0.2.0 github.com/alecthomas/chroma v0.10.0 + github.com/alecthomas/chroma/v2 v2.2.0 github.com/fsnotify/fsnotify v1.8.0 github.com/gomarkdown/markdown v0.0.0-20240730141124-034f12af3bf6 + github.com/yuin/goldmark v1.7.8 + github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc + go.abhg.dev/goldmark/toc v0.10.0 go.uber.org/zap v1.27.0 ) require ( github.com/BurntSushi/toml v1.2.1 // indirect - github.com/dlclark/regexp2 v1.4.0 // indirect + github.com/dlclark/regexp2 v1.7.0 // indirect github.com/stretchr/testify v1.9.0 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/sys v0.13.0 // indirect diff --git a/go.sum b/go.sum index 9502cb0..15f4a80 100644 --- a/go.sum +++ b/go.sum @@ -5,11 +5,16 @@ github.com/adrg/frontmatter v0.2.0 h1:/DgnNe82o03riBd1S+ZDjd43wAmC6W35q67NHeLkPd github.com/adrg/frontmatter v0.2.0/go.mod h1:93rQCj3z3ZlwyxxpQioRKC1wDLto4aXHrbqIsnH9wmE= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY= +github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= +github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae h1:zzGwJfFlFGD94CyyYwCJeSuD32Gj9GTaSi5y9hoVzdY= +github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gomarkdown/markdown v0.0.0-20240730141124-034f12af3bf6 h1:ZPy+2XJ8u0bB3sNFi+I72gMEMS7MTg7aZCCXPOjV8iw= @@ -20,6 +25,13 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= +go.abhg.dev/goldmark/toc v0.10.0 h1:de3LrIimwtGhBMKh7aEl1c6n4XWwOdukIO5wOAMYZzg= +go.abhg.dev/goldmark/toc v0.10.0/go.mod h1:OpH0qqRP9v/eosCV28ZeqGI78jZ8rri3C7Jh8fzEo2M= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= @@ -35,3 +47,5 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= +pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/html/highlight.go b/html/highlight.go index d198ad0..621abdb 100644 --- a/html/highlight.go +++ b/html/highlight.go @@ -1,80 +1 @@ package html - -import ( - "io" - - "github.com/gomarkdown/markdown/ast" - mdhtml "github.com/gomarkdown/markdown/html" - - "github.com/alecthomas/chroma" - "github.com/alecthomas/chroma/formatters/html" - "github.com/alecthomas/chroma/lexers" - "github.com/alecthomas/chroma/styles" -) - -type Highlighter struct { - htmlFormatter *html.Formatter - highlightStyle *chroma.Style -} - -func (h Highlighter) htmlHighlight(w io.Writer, source, lang, defaultLang string) error { - if lang == "" { - lang = defaultLang - } - - l := lexers.Get(lang) - if l == nil { - l = lexers.Analyse(source) - } - if l == nil { - l = lexers.Fallback - } - l = chroma.Coalesce(l) - - it, err := l.Tokenise(nil, source) - if err != nil { - return err - } - return h.htmlFormatter.Format(w, h.highlightStyle, it) -} - -func (h Highlighter) renderCode(w io.Writer, codeBlock *ast.CodeBlock, entering bool) { - defaultLang := "" - lang := string(codeBlock.Info) - h.htmlHighlight(w, string(codeBlock.Literal), lang, defaultLang) -} - -func (h Highlighter) myRenderHook(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) { - if code, ok := node.(*ast.CodeBlock); ok { - h.renderCode(w, code, entering) - return ast.GoToNext, true - } - return ast.GoToNext, false -} - -type opts struct { -} - -func newRenderer(hasToc bool) *mdhtml.Renderer { - - htmlFormatter := html.New(html.WithClasses(true), html.TabWidth(2)) - - styleName := "monokailight" - highlightStyle := styles.Get(styleName) - - h := Highlighter{ - htmlFormatter: htmlFormatter, - highlightStyle: highlightStyle, - } - - flags := mdhtml.CommonFlags | mdhtml.FootnoteReturnLinks - if hasToc { - flags = flags | mdhtml.TOC - } - - opts := mdhtml.RendererOptions{ - Flags: flags, - RenderNodeHook: h.myRenderHook, - } - return mdhtml.NewRenderer(opts) -} diff --git a/html/html.go b/html/html.go index 40a6ad5..bd3139e 100644 --- a/html/html.go +++ b/html/html.go @@ -1,14 +1,63 @@ package html import ( - "github.com/gomarkdown/markdown" - "github.com/gomarkdown/markdown/parser" + "bytes" + + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/renderer/html" + "go.abhg.dev/goldmark/toc" + + "github.com/alecthomas/chroma/v2" + chromahtml "github.com/alecthomas/chroma/v2/formatters/html" + chromastyles "github.com/alecthomas/chroma/v2/styles" + highlighting "github.com/yuin/goldmark-highlighting/v2" ) func MDToHTML(md []byte, hasToc bool) []byte { - extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock | parser.Footnotes - p := parser.NewWithExtensions(extensions) + style := chromastyles.Get("autumn") + style, err := style.Builder().Add(chroma.Background, "bg:#f0f0f0").Build() + if err != nil { + return nil + } + + extensions := []goldmark.Extender{ + extension.GFM, + extension.Footnote, + highlighting.NewHighlighting( + highlighting.WithCustomStyle(style), + highlighting.WithFormatOptions( + chromahtml.WrapLongLines(true), + chromahtml.InlineCode(true), + chromahtml.TabWidth(4), + chromahtml.PreventSurroundingPre(false), + ), + ), + } + + if hasToc { + extensions = append(extensions, &toc.Extender{ + Compact: true, + }) + } + + converter := goldmark.New( + goldmark.WithExtensions(extensions...), + goldmark.WithParserOptions( + parser.WithAutoHeadingID(), + ), + goldmark.WithRendererOptions( + html.WithHardWraps(), + html.WithXHTML(), + ), + ) + + var buf bytes.Buffer + err = converter.Convert(md, &buf) + if err != nil { + return nil + } - renderer := newRenderer(hasToc) - return markdown.ToHTML(md, p, renderer) + return buf.Bytes() } diff --git a/pages/pages.go b/pages/pages.go index 8d789b3..542491b 100644 --- a/pages/pages.go +++ b/pages/pages.go @@ -128,9 +128,9 @@ func sortNavBar(nav []Navigation) { // see no evil, speak no evil sort.Slice(nav, func(i, j int) bool { order := map[string]int{ - "home": 1, - "posts": 2, - "about": 3, + "Home": 1, + "Posts": 2, + "Ethos": 3, } // Get the order value, default to a high number if not in the predefined order diff --git a/public/ethos.html b/public/ethos.html index ddbf7ac..34a4417 100644 --- a/public/ethos.html +++ b/public/ethos.html @@ -25,59 +25,42 @@ +
-

ethos

+

Ethos

This Site Has an Ethos

- -

The internet wasn’t built to be what it is today: centralised, corporate, and relentlessly intrusive. Though it started as a decentralised network of connected devices, the modern internet is dominated by a handful of tech giants. They control the infrastructure, the platforms, and the data that define our digital lives. This centralised system has turned users into products—our information is harvested, commodified, and sold in the name of profit.

- -

This site is my response to that dominance. It’s my small, self-sustained corner of the web, free from the control of big tech and corporate interests.

- -

The Problem with Today’s Internet

- -

The internet is marketed as a place of freedom and connectivity, but increasingly, it’s neither. Users flock to major platforms—social media, search engines, cloud services—only to trade privacy for convenience. Social networks offer the illusion of community, while search engines pretend to help us navigate the vast web. In reality, these sites funnel us toward paid content, using our data to feed surveillance-driven algorithms.

- -

Data on these platforms isn’t a protected asset; it’s a resource for exploitation. Your personal information becomes a data point, sold and repackaged for targeted advertising because big tech hasn’t found any other way to drive profit without compromising privacy.

- +

The internet wasn't built to be what it is today: centralised, corporate, and relentlessly intrusive. Though it started as a decentralised network of connected devices, the modern internet is dominated by a handful of tech giants. They control the infrastructure, the platforms, and the data that define our digital lives. This centralised system has turned users into products—our information is harvested, commodified, and sold in the name of profit.

+

This site is my response to that dominance. It's my small, self-sustained corner of the web, free from the control of big tech and corporate interests.

+

The Problem with Today's Internet

+

The internet is marketed as a place of freedom and connectivity, but increasingly, it's neither. Users flock to major platforms—social media, search engines, cloud services—only to trade privacy for convenience. Social networks offer the illusion of community, while search engines pretend to help us navigate the vast web. In reality, these sites funnel us toward paid content, using our data to feed surveillance-driven algorithms.

+

Data on these platforms isn't a protected asset; it's a resource for exploitation. Your personal information becomes a data point, sold and repackaged for targeted advertising because big tech hasn't found any other way to drive profit without compromising privacy.

A Different Approach: Owning Your Infrastructure

- -

This site is hosted on a Lenovo M910Q—a single-node Kubernetes cluster with an Intel i5-6500T processor and 8GB of RAM. I bought it in 2021 for £189.99. For comparison, running a similar setup on AWS with a c6g.xlarge instance (4 vCPUs, 8 GB memory) would cost around $1,600 for three years, not including storage network, or electricity fees. So, I’d argue that despite AWS advertising its low cost, cheap self-hosted computing for small projects is advantageous unless you need elasticity.

- -

This single device has served me well for years, hosting various services like Tiny Tiny RSS, Miniflux, Ergochat, Vaultwarden, Immich, NextCloud, Matrix, cgit, and others. Currently, it only runs Miniflux, Ergochat, cgit, and this site. While some may argue that this isn’t a “production-ready” setup due to the lack of backups or failover nodes, I’m comfortable with it—I’ve yet to have any accidental downtime. If Miniflux is down, I’ll start it back up. IRC is ephemeral, and this site is version-controlled on SourceHut. I might not have enough regional replication to manage 99.999% uptime, but I have plans to reinstate these services.

- -

Taking a hands-on approach means I’ve learned a lot more than if I delegated to a cloud provider. Managing Kubernetes, setting up firewalls, configuring DNS and DHCP—these tasks teach skills often abstracted away by cloud services.

- +

This site is hosted on a Lenovo M910Q—a single-node Kubernetes cluster with an Intel i5-6500T processor and 8GB of RAM. I bought it in 2021 for £189.99. For comparison, running a similar setup on AWS with a c6g.xlarge instance (4 vCPUs, 8 GB memory) would cost around $1,600 for three years, not including storage network, or electricity fees. So, I'd argue that despite AWS advertising its low cost, cheap self-hosted computing for small projects is advantageous unless you need elasticity.

+

This single device has served me well for years, hosting various services like Tiny Tiny RSS, Miniflux, Ergochat, Vaultwarden, Immich, NextCloud, Matrix, cgit, and others. Currently, it only runs Miniflux, Ergochat, cgit, and this site. While some may argue that this isn't a "production-ready" setup due to the lack of backups or failover nodes, I'm comfortable with it—I've yet to have any accidental downtime. If Miniflux is down, I'll start it back up. IRC is ephemeral, and this site is version-controlled on SourceHut. I might not have enough regional replication to manage 99.999% uptime, but I have plans to reinstate these services.

+

Taking a hands-on approach means I've learned a lot more than if I delegated to a cloud provider. Managing Kubernetes, setting up firewalls, configuring DNS and DHCP—these tasks teach skills often abstracted away by cloud services.

The Design Philosophy: Minimalism, Accessibility, and Longevity

- -

I’ve chosen a minimal design for this site. It uses almost no JavaScript, serves only static content, and is designed to work consistently across browsers and devices. The goal is simple: create a site that respects users’ time, attention, and data.

- -

This isn’t a space for tracking, data collection, or ads. Unlike the bloated, ad-filled experiences on many websites, this site prioritises speed and privacy. It’s designed to be here for the long term, a place that will continue to work as technology evolves.

- +

I've chosen a minimal design for this site. It uses almost no JavaScript, serves only static content, and is designed to work consistently across browsers and devices. The goal is simple: create a site that respects users' time, attention, and data.

+

This isn't a space for tracking, data collection, or ads. Unlike the bloated, ad-filled experiences on many websites, this site prioritises speed and privacy. It's designed to be here for the long term, a place that will continue to work as technology evolves.

A Different Kind of Content

- -

In an age of algorithmic content feeds, low-effort Medium posts, and AI-generated text, I aim to provide something more thoughtful and human. This site is a place to share what I’ve learned, especially in tech, cloud development, and working with Go.

- +

In an age of algorithmic content feeds, low-effort Medium posts, and AI-generated text, I aim to provide something more thoughtful and human. This site is a place to share what I've learned, especially in tech, cloud development, and working with Go.

Over time, I intend to curate this content—merging, filtering, and removing outdated posts to keep it relevant.

-

Why It Matters

- -

The skills and knowledge I’ve gained from running this setup go beyond cost savings. By managing my own infrastructure, I’ve reclaimed a sense of ownership and independence that centralised platforms discourage. I’m not here to sell anything or grow an audience—I’m here to carve out a space on the web that’s truly mine.

- -

If you’re interested in building something like this for yourself, I’ll be sharing what I know. You don’t need a massive budget or a cloud subscription. You just need a willingness to learn, experiment, and take control of your digital presence.

+

The skills and knowledge I've gained from running this setup go beyond cost savings. By managing my own infrastructure, I've reclaimed a sense of ownership and independence that centralised platforms discourage. I'm not here to sell anything or grow an audience—I'm here to carve out a space on the web that's truly mine.

+

If you're interested in building something like this for yourself, I'll be sharing what I know. You don't need a massive budget or a cloud subscription. You just need a willingness to learn, experiment, and take control of your digital presence.