Files
beardrive/internal/webapp/markdown.go
Snow LeeandClaude Fable 5 2d29ff8aac feat: web viewer, project-local config, selective sync, Claude Code plugin
- sfs-web (cmd/sfs-web, internal/webapp): read-only Obsidian-style web UI
  serving a local folder (default) or an sfs remote; markdown rendering
  with [[wikilinks]], task lists and tables, file downloads with ETags,
  per-file provenance from the journals; added to goreleaser builds
- .sfs project file (internal/config): per-folder volume/remote/include
  settings that travel with the folder, win over the global registry, and
  never sync; daemon picks up edits live
- .sfsignore + include lists (internal/syncer): gitignore-style selective
  sync with ! re-includes, applied symmetrically in scan and materialize;
  newly ignored files stop syncing without being deleted anywhere
- Claude Code plugin (plugin/, .claude-plugin/): sfs skill, /sfs:mount and
  /sfs:status commands, turn-boundary sync hooks (blocking pull on prompt,
  async push on stop); installable via the repo's marketplace manifest
- CLAUDE.md and .claude project settings for Claude Code development

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHEUaYfFHhmDvqLYw74Ehz
2026-07-07 14:05:36 -07:00

43 lines
1.2 KiB
Go

package webapp
import (
"bytes"
"net/url"
"regexp"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
)
var md = goldmark.New(
goldmark.WithExtensions(extension.GFM),
goldmark.WithParserOptions(parser.WithAutoHeadingID()),
)
// wikiRe matches Obsidian-style [[target]] and [[target|label]] links.
var wikiRe = regexp.MustCompile(`\[\[([^\]|]+)(?:\|([^\]]+))?\]\]`)
// expandWikilinks rewrites [[target]] to a markdown link with a wiki: URL;
// the frontend resolves the target against the file tree by basename.
func expandWikilinks(src []byte) []byte {
return wikiRe.ReplaceAllFunc(src, func(m []byte) []byte {
g := wikiRe.FindSubmatch(m)
target, label := g[1], g[2]
if len(label) == 0 {
label = target
}
return []byte("[" + string(label) + "](wiki:" + url.PathEscape(string(target)) + ")")
})
}
// RenderMarkdown converts markdown to HTML (GFM + wikilinks). Raw HTML in
// the source is escaped by goldmark's safe default.
func RenderMarkdown(src []byte) (string, error) {
var buf bytes.Buffer
if err := md.Convert(expandWikilinks(src), &buf); err != nil {
return "", err
}
return buf.String(), nil
}