Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

Notes for upgrading an existing GoBlog site across releases that need manual steps. Newest first.

## Code highlighting moved to the server

Fenced code in posts and pages is now coloured when the markdown is rendered, and goblog no longer loads highlight.js. Two things to do:

- **Label your code fences.** A fence with no language (```` ``` ```` rather than ```` ```go ````) is guessed, and chroma's guess only works for code with a clear signature, such as Go's `package` line or a `#!/bin/bash` shebang. Anything else renders as plain text until you add the language: ```` ```bash ````, ```` ```python ````, ```` ```yaml ```` and so on ([chroma's list](https://github.com/alecthomas/chroma#supported-languages)).
- **Themes:** drop any `hljs.highlightAll()` call from `footer.html`; `hljs` is no longer defined, so the call throws. To restyle code, override the `.chroma` classes in your `goblog.css`; the defaults are in `www/css/base.css`.

## Moving an existing site into `GOBLOG_DATA_DIR`

Optional, from 0.14.0. A site set up before `GOBLOG_DATA_DIR` existed keeps its files in goblog's working directory (`/go/src/github.com/compscidr/goblog` in the Docker image), usually with one bind mount per path. It keeps working as it is. To move it to a single data directory instead:
Expand Down
13 changes: 12 additions & 1 deletion blog/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"bytes"
"html/template"

chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
"github.com/microcosm-cc/bluemonday"
"github.com/yuin/goldmark"
highlighting "github.com/yuin/goldmark-highlighting/v2"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/renderer/html"
)
Expand All @@ -15,8 +17,17 @@ import (
// iframes, Instagram scripts) depend on it. This is a deliberate decision,
// recorded in docs/superpowers/specs/2026-09-22-server-side-markdown-design.md;
// a post or page can therefore run JavaScript on the site.
//
// Fenced code is coloured here rather than by highlight.js in the browser
// (#625). Almost no fence in existing posts names its language, so an
// unlabelled one is guessed; a guess chroma cannot make renders as plain
// text, and labelling that fence fixes it. The colours are CSS classes,
// styled in www/css/base.css so a theme can restyle them.
var authorMarkdown = goldmark.New(
goldmark.WithExtensions(extension.GFM),
goldmark.WithExtensions(extension.GFM, highlighting.NewHighlighting(
highlighting.WithGuessLanguage(true),
highlighting.WithFormatOptions(chromahtml.WithClasses(true)),
)),
goldmark.WithRendererOptions(html.WithUnsafe()),
)

Expand Down
26 changes: 25 additions & 1 deletion blog/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,37 @@ func TestPostHTML_KeepsRawHTML(t *testing.T) {
func TestPostHTML_RendersGFM(t *testing.T) {
p := Post{Content: "# Title\n\nSome **bold** and a [link](/x).\n\n| a | b |\n|---|---|\n| 1 | 2 |\n\n```go\nfunc main() {}\n```\n\n- [x] done\n- [ ] todo\n\n~~struck~~\n"}
got := string(p.HTML())
for _, want := range []string{"<h1", "<strong>bold</strong>", `<a href="/x">link</a>`, "<table>", "<td>1</td>", "<code", "func main()", `type="checkbox"`, "<del>struck</del>"} {
for _, want := range []string{"<h1", "<strong>bold</strong>", `<a href="/x">link</a>`, "<table>", "<td>1</td>", "<code", "main", `type="checkbox"`, "<del>struck</del>"} {
if !strings.Contains(got, want) {
t.Errorf("missing %q in %q", want, got)
}
}
}

// TestPostHTML_HighlightsCode: fenced code is coloured on the server (#625).
// A labelled fence uses its language, an unlabelled one is guessed, one that
// cannot be guessed is plain text, and code is escaped either way.
func TestPostHTML_HighlightsCode(t *testing.T) {
cases := map[string]string{
"labelled": "```go\nfunc main() {}\n```\n",
"guessed": "```\npackage main\n\nfunc main() {}\n```\n",
"unguessable": "```\nhello <script>alert(1)</script>\n```\n",
}
for name, src := range cases {
got := string(Post{Content: src}.HTML())
if !strings.Contains(got, `<pre class="chroma">`) {
t.Errorf("%s: not rendered by chroma: %q", name, got)
}
if strings.Contains(got, "<script>") {
t.Errorf("%s: code is not escaped: %q", name, got)
}
coloured := strings.Contains(got, `class="k`) // any keyword token
if coloured != (name != "unguessable") {
t.Errorf("%s: coloured = %v: %q", name, coloured, got)
}
}
}

// TestCommentHTML_Sanitises: commenters are anonymous, so a comment may use
// ordinary markdown but never raw HTML, scripts, embeds or javascript: URLs.
func TestCommentHTML_Sanitises(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ require (
)

require (
github.com/alecthomas/chroma/v2 v2.27.0
github.com/extism/go-sdk v1.7.1
github.com/gobwas/glob v1.0.0
github.com/microcosm-cc/bluemonday v1.0.27
github.com/tetratelabs/wazero v1.12.0
github.com/traefik/yaegi v0.16.1
github.com/yuin/goldmark v1.8.6
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
golang.org/x/crypto v0.58.0
gorm.io/driver/postgres v1.6.3
)
Expand All @@ -34,6 +36,7 @@ require (
github.com/bytedance/sonic v1.15.2 // indirect
github.com/bytedance/sonic/loader v0.5.1 // indirect
github.com/cloudwego/base64x v0.1.7 // indirect
github.com/dlclark/regexp2/v2 v2.2.1 // indirect
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/sse v1.1.1 // indirect
Expand Down
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs=
github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8=
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
Expand All @@ -13,6 +21,10 @@ github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4
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/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0=
github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE=
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q=
github.com/extism/go-sdk v1.7.1 h1:lWJos6uY+tRFdlIHR+SJjwFDApY7OypS/2nMhiVQ9Sw=
Expand Down Expand Up @@ -58,6 +70,8 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca h1:T54Ema1DU8ngI+aef9ZhAhNGQhcRTrWxVeG07F+c/Rw=
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
Expand Down Expand Up @@ -124,8 +138,11 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.8.6 h1:d0VcaP1sx9GkFVkoW+KtggpGi2KZ965i14b0+bDQST4=
github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
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.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8=
go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
Expand Down
4 changes: 0 additions & 4 deletions templates/shared/_head.html
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,6 @@
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/simplemde/1.11.2/simplemde.min.js" integrity="sha512-ksSfTk6JIdsze75yZ8c+yDVLu09SNefa9IicxEE+HZvWo9kLPY1vrRlmucEMHQReWmEdKqusQWaDMpkTb3M2ug==" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/simplemde/1.11.2/simplemde.min.css" integrity="sha512-lB03MbtC3LxImQ+BKnZIyvVHTQ8SSmQ15AhVh5U/+CCp4wKtZMvgLGXbZIjIBBbnKsmk3/6n2vcF8a9CtVVSfA==" crossorigin="anonymous" />
{{ end }}
{{ if or .admin_page .post }}
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js" integrity="sha512-EBLzUL8XLl+va/zAsmXwS7Z2B1F9HUHkZwyS/VKwh3S7T/U0nF4BaU29EP/ZSf6zgiIxYAnKLu6bJ8dqpmX5uw==" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css" integrity="sha512-hasIneQUHlh06VNBe7f6ZcHmeRTLIaQWFd43YriJ0UND19bvYRauxthDg8E4eVNPm9bRUhr5JGeqH7FRFXQu5g==" crossorigin="anonymous" />
{{ end }}
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.8/js/bootstrap.min.js" integrity="sha512-nKXmKvJyiGQy343jatQlzDprflyB5c+tKCzGP3Uq67v+lmzfnZUi/ZT+fc6ITZfSC5HhaBKUIvr/nTLCV+7F+Q==" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.8/css/bootstrap.min.css" integrity="sha512-2bBQCjcnw658Lho4nlXJcc6WkV/UxpE/sAokbXPxQNGqmNdQrWqtw26Ns9kFF/yG792pKR1Sx8/Y1Lf1XN4GKA==" crossorigin="anonymous" />
Expand Down
6 changes: 0 additions & 6 deletions themes/default/templates/footer.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ <h3 class="text-left"><a href="{{ .recent.Permalink }}" title="{{ .recent.Title
</p>
</div>
</div>
{{ if or .admin_page .post }}
<script>
// highlight.js is deferred, so it is only defined once parsing is done.
document.addEventListener("DOMContentLoaded", function () { hljs.highlightAll(); });
</script>
{{ end }}
{{ with index .settings "custom_footer_code" }}{{ if .Value }}
{{ .Value | rawHTML }}
{{ end }}{{ end }}
Expand Down
79 changes: 79 additions & 0 deletions www/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,82 @@
.striped--light-gray:nth-child(odd) {
background-color: #eee;
}

/* Code highlighting. Posts and pages colour fenced code on the server with
chroma (#625); these are chroma's classes in its "github" style, generated
by chroma's html.Formatter.WriteCSS. A theme restyles code by overriding
them in its goblog.css, which loads after this file. The padding and
scrolling match what highlight.js gave code blocks before. */
.chroma { overflow-x: auto; padding: 1em; }
/* PreWrapper */ .chroma { background-color: #f7f7f7; -webkit-text-size-adjust: none; }
/* Error */ .chroma .err { color: #f6f8fa; background-color: #82071e }
/* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit }
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
/* LineHighlight */ .chroma .hl { background-color: #dedede }
/* LineNumbersTable */ .chroma .lnt { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* LineNumbers */ .chroma .ln { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* Line */ .chroma .line { display: flex; }
/* Keyword */ .chroma .k { color: #cf222e }
/* KeywordConstant */ .chroma .kc { color: #cf222e }
/* KeywordDeclaration */ .chroma .kd { color: #cf222e }
/* KeywordNamespace */ .chroma .kn { color: #cf222e }
/* KeywordPseudo */ .chroma .kp { color: #cf222e }
/* KeywordReserved */ .chroma .kr { color: #cf222e }
/* KeywordType */ .chroma .kt { color: #cf222e }
/* NameAttribute */ .chroma .na { color: #1f2328 }
/* NameClass */ .chroma .nc { color: #1f2328 }
/* NameConstant */ .chroma .no { color: #0550ae }
/* NameDecorator */ .chroma .nd { color: #0550ae }
/* NameEntity */ .chroma .ni { color: #6639ba }
/* NameLabel */ .chroma .nl { color: #990000; font-weight: bold }
/* NameNamespace */ .chroma .nn { color: #24292e }
/* NameOther */ .chroma .nx { color: #1f2328 }
/* NameTag */ .chroma .nt { color: #0550ae }
/* NameBuiltin */ .chroma .nb { color: #6639ba }
/* NameBuiltinPseudo */ .chroma .bp { color: #6a737d }
/* NameVariable */ .chroma .nv { color: #953800 }
/* NameVariableClass */ .chroma .vc { color: #953800 }
/* NameVariableGlobal */ .chroma .vg { color: #953800 }
/* NameVariableInstance */ .chroma .vi { color: #953800 }
/* NameVariableMagic */ .chroma .vm { color: #953800 }
/* NameFunction */ .chroma .nf { color: #6639ba }
/* NameFunctionMagic */ .chroma .fm { color: #6639ba }
/* LiteralString */ .chroma .s { color: #0a3069 }
/* LiteralStringAffix */ .chroma .sa { color: #0a3069 }
/* LiteralStringBacktick */ .chroma .sb { color: #0a3069 }
/* LiteralStringChar */ .chroma .sc { color: #0a3069 }
/* LiteralStringDelimiter */ .chroma .dl { color: #0a3069 }
/* LiteralStringDoc */ .chroma .sd { color: #0a3069 }
/* LiteralStringDouble */ .chroma .s2 { color: #0a3069 }
/* LiteralStringEscape */ .chroma .se { color: #0a3069 }
/* LiteralStringHeredoc */ .chroma .sh { color: #0a3069 }
/* LiteralStringInterpol */ .chroma .si { color: #0a3069 }
/* LiteralStringOther */ .chroma .sx { color: #0a3069 }
/* LiteralStringRegex */ .chroma .sr { color: #0a3069 }
/* LiteralStringSingle */ .chroma .s1 { color: #0a3069 }
/* LiteralStringSymbol */ .chroma .ss { color: #032f62 }
/* LiteralNumber */ .chroma .m { color: #0550ae }
/* LiteralNumberBin */ .chroma .mb { color: #0550ae }
/* LiteralNumberFloat */ .chroma .mf { color: #0550ae }
/* LiteralNumberHex */ .chroma .mh { color: #0550ae }
/* LiteralNumberInteger */ .chroma .mi { color: #0550ae }
/* LiteralNumberIntegerLong */ .chroma .il { color: #0550ae }
/* LiteralNumberOct */ .chroma .mo { color: #0550ae }
/* Operator */ .chroma .o { color: #0550ae }
/* OperatorWord */ .chroma .ow { color: #0550ae }
/* OperatorReserved */ .chroma .or { color: #0550ae }
/* Punctuation */ .chroma .p { color: #1f2328 }
/* Comment */ .chroma .c { color: #57606a }
/* CommentHashbang */ .chroma .ch { color: #57606a }
/* CommentMultiline */ .chroma .cm { color: #57606a }
/* CommentSingle */ .chroma .c1 { color: #57606a }
/* CommentSpecial */ .chroma .cs { color: #57606a }
/* CommentPreproc */ .chroma .cp { color: #57606a }
/* CommentPreprocFile */ .chroma .cpf { color: #57606a }
/* GenericDeleted */ .chroma .gd { color: #82071e; background-color: #ffebe9 }
/* GenericEmph */ .chroma .ge { color: #1f2328 }
/* GenericInserted */ .chroma .gi { color: #116329; background-color: #dafbe1 }
/* GenericOutput */ .chroma .go { color: #1f2328 }
/* GenericUnderline */ .chroma .gl { text-decoration: underline }
/* TextWhitespace */ .chroma .w { color: #ffffff }
Loading