feat: image support in posts served from an assets directory - #89
Merged
Conversation
Standard markdown images and wikilink image embeds resolve relative
paths to {BlogRoot}images/<path>. Adds parser.WithBlogRoot and the
config.AssetsDir option type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Registers {BlogRoot}images/ when config.WithAssetsDir is supplied and
points at an existing directory. Directory listings return 404. The
generator now passes its BlogRoot to the parser.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Defaults to <posts>/images and is silently ignored when missing. The directory is opened with os.OpenRoot so symlinks cannot escape it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
harrydayexe
deleted the
88-feat-image-support-in-posts-standard-markdown-wikilink-embeds-served-from-an-assets-directory
branch
September 13, 2026 17:02
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #88
Summary
Posts can now include images, written either as standard markdown or as wikilink embeds. They are served from an assets directory by
goblog serveand copied into the output bygoblog generate.GeneratedBlogis unchanged.All of these render with
src="{BlogRoot}images/pipeline.png".Changes
config.AssetsDir(struct{ FS fs.FS }) andconfig.WithAssetsDir(fs.FS), added as aBaseOption. That lets it reachserver.Handler,server.New(viaAsServerOption) andoutputter.NewDirectoryWriter(viaAsGeneratorOption).AssetsDir.Enabled()returns false when the FS is nil or its root isn't a directory, so the feature switches off without an error.Config.BlogRootandparser.WithBlogRoot. An AST transformer rewritesast.Image.Destination, and the wikilink resolver (renamedwikilinkExtender/wikilinkResolver) handles image embeds. Both use the sameassetURLrules:images/foo.png,foo.pngandsub/foo.pngare rewritten to{BlogRoot}images/.... Query strings and fragments are kept./root-relativeand//protocol-relativepaths are left untouched...segment are left untouched and a warning is logged.![[notes.txt]]still renders as plain text. The image-extension list matches the wikilink renderer's.BlogRootto the parser config.GET {root}/images/useshttp.FileServerFS, so it only exists under the blog root. Requests for a directory return 404, so there are no directory listings. Cache-Control applies as on every other route.<out>/images/, in raw mode too. I didn't useos.CopyFSbecause it refuses to overwrite existing files, which would break re-runninggenerateinto the same output directory. The copy overwrites, and it skips non-regular entries and symlinks that escape the root, logging a warning for each.--assets-dirflag, defaulting to<posts>/images. A missing directory is ignored; a path that exists but isn't a directory is an error. The directory is opened withos.OpenRoot, notos.DirFS. The issue assumed anfs.FSwon't follow symlinks out of the tree, butos.DirFSdoes;os.Rootrefuses to.Decision: missing image files fail silently
This is the open question from the issue. An image path that points to a file missing from the assets directory produces no warning and no error. This matches how links behave:
[[#heading]]and[text](#anchor)targets aren't checked either. We may revisit this later (for example, a build-time warning through the parser logger orParseErrors). The behaviour is documented inpkg/parser/doc.goand the README.Other judgement calls
--assets-diroutside the posts tree isn't watched. The reasoning is written up inpkg/watcher/doc.go.servestarts. The CLI opens theos.Rootonce, so a directory created later is only picked up after a restart. This is in the README.![[foo.png]]to get the filename as alt text. In fact the wikilink renderer leaves outaltwhen the label equals the target. The docs say this and recommend the|labelform.Testing
assetURL, and both syntaxes withBlogRootset to/and/blog/. Covers relative, bare, subdirectory, root-relative, absolute,..and empty paths, quote escaping insrc, non-image embeds, and heading anchors (no regression).Content-Type: image/pngand Cache-Control. Also tested: subdirectories, 404 for missing files and directories, 404 for/images/when root is/blog/, traversal attempts and a symlink escape viaos.Root, a.htmlin a filename not being stripped, and no route when the option is nil, missing or not supplied.images/directory when disabled, and an escaping symlink skipped.BlogRootreaches the renderedsrc.OpenAssetsDirdefault, explicit, missing and not-a-directory cases.TestServe_Images(container). It compiles, but I couldn't run it locally because Docker isn't available, so CI will be its first real run.goblog generate --root-path /blog/copiesimages/sub/a.pngand renders both syntaxes with/blog/images/sub/a.png.🤖 Generated with Claude Code
Changelog (#89)
✨ New Features
📚 Documentation
🧪 Tests