From 5bf2312cf9569a4797cb3997430fca7aaa492f49 Mon Sep 17 00:00:00 2001 From: Nikita Shubin Date: Sun, 26 Apr 2026 10:24:59 +0300 Subject: [PATCH] FIXME: hugolib: markdown_title: add title from markdown Getting title from the page itself, as first encountered header in markdown file. Currently it's inefficient as we parsing page twice, the first in some ealier point when hugo renders markdown, so it should be embedded somewhere in that place. But currently i don't care about if a PoC implementation. Currently it's required to use '.MarkdownTitleInfo.Title' instead of '.Title', where appropriate. TODO: - make it config controllable - embed it into .Title: - fallback to Meta if no markdown title available - or do the opposite and use it if no Meta provided Signed-off-by: Nikita Shubin --- hugolib/hugo_sites.go | 35 ++++++++++++++++++++ hugolib/markdown_title.go | 68 +++++++++++++++++++++++++++++++++++++++ hugolib/page.go | 4 +++ hugolib/page__common.go | 2 ++ hugolib/page__new.go | 6 ++++ hugolib/site.go | 4 +++ 6 files changed, 119 insertions(+) create mode 100644 hugolib/markdown_title.go diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 87c04035d..eea3583a5 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -84,6 +84,8 @@ type HugoSites struct { gitInfo *gitInfo codeownerInfo *codeownerInfo + markdownTitleInfo *markdownTitleInfo + // As loaded from the /data dirs data map[string]any @@ -332,6 +334,8 @@ type hugoSitesInit struct { // Loads the Git info and CODEOWNERS for all the pages if enabled. gitInfo hsync.FuncResetter + + markdownTitleInfo hsync.FuncResetter } func (h *HugoSites) Data() map[string]any { @@ -401,6 +405,18 @@ func (h *HugoSites) codeownersForPage(p page.Page) ([]string, error) { return h.codeownerInfo.forPage(p), nil } +func (h *HugoSites) markdownTitleInfoForPage(p page.Page) (*MarkdownTitleInfo, error) { + if err := h.init.markdownTitleInfo.Do(context.Background()); err != nil { + return nil, err + } + + if h.markdownTitleInfo == nil { + return nil, nil + } + + return h.markdownTitleInfo.forPage(p), nil +} + func (h *HugoSites) reportProgress(f func() (state terminal.ProgressState, progress float64)) { h.progressReporter.mu.Lock() defer h.progressReporter.mu.Unlock() @@ -568,6 +584,25 @@ func (h *HugoSites) loadGitInfo() error { return nil } +func (h *HugoSites) loadMarkdownTitleInfo() error { + // TODO: Make it configurable + // if h.Configs.Base.EnableMarkdownTitle { + // cfg := gitInfoConfig{ + // Deps: h.Deps, + // Modules: h.Configs.Modules, + // GitInfoCache: h.Configs.FileCaches.ModuleGitInfoCache(), + // Logger: h.Log, + // } + mt, err := newMarkdownTitleInfo(h.Deps.Conf.DirsBase().ContentDir) + if err != nil { + return err + } else { + h.markdownTitleInfo = mt + } + // } + return nil +} + // Reset resets the sites and template caches etc., making it ready for a full rebuild. func (h *HugoSites) reset() { h.fatalErrorHandler = &fatalErrorHandler{ diff --git a/hugolib/markdown_title.go b/hugolib/markdown_title.go new file mode 100644 index 000000000..fe05a758a --- /dev/null +++ b/hugolib/markdown_title.go @@ -0,0 +1,68 @@ +package hugolib + +import ( + "path/filepath" + "strings" + "os" + + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/text" + "github.com/yuin/goldmark/ast" + "github.com/leonhfr/goldmark-frontmatter" + + "github.com/gohugoio/hugo/resources/page" +) + +type MarkdownTitleInfo struct { + Title string `json:"title"` // Title +} + +type MarkdownTitleMap map[string]*MarkdownTitleInfo + +type markdownTitleInfo struct { + contentDir string + Map MarkdownTitleMap +} + +func findFirstH1(mdContent []byte) string { + md := goldmark.New( + goldmark.WithExtensions( + &frontmatter.Extender{}, + ), + ) + + doc := md.Parser().Parse(text.NewReader(mdContent)) + + var headingText string + ast.Walk(doc, func(node ast.Node, entering bool) (ast.WalkStatus, error) { + if entering && node.Kind() == ast.KindHeading { + if heading, ok := node.(*ast.Heading); ok && heading.Level == 1 { + headingText = string(heading.Text(mdContent)) + return ast.WalkStop, nil + } + } + + return ast.WalkContinue, nil + }) + + return strings.TrimSpace(headingText) +} + +func (m *markdownTitleInfo) forPage(p page.Page) *MarkdownTitleInfo { + name := strings.TrimPrefix(filepath.ToSlash(p.File().Filename()), m.contentDir) + + source, err := os.ReadFile(name) + if err != nil { + return nil + } + + title := findFirstH1([]byte(source)) + + return &MarkdownTitleInfo{Title: title} +} + +func newMarkdownTitleInfo(_contentDir string) (*markdownTitleInfo, error) { + contentDir := _contentDir + + return &markdownTitleInfo{contentDir: contentDir}, nil +} \ No newline at end of file diff --git a/hugolib/page.go b/hugolib/page.go index 759e4fe36..742ff343d 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -325,6 +325,10 @@ func (ps *pageState) CodeOwners() []string { return ps.codeowners } +func (p *pageState) MarkdownTitleInfo() *MarkdownTitleInfo { + return p.markdownTitleInfo +} + // GetTerms gets the terms defined on this page in the given taxonomy. // The pages returned will be ordered according to the front matter. func (ps *pageState) GetTerms(taxonomy string) page.Pages { diff --git a/hugolib/page__common.go b/hugolib/page__common.go index 2ea07c9d8..42965e8a0 100644 --- a/hugolib/page__common.go +++ b/hugolib/page__common.go @@ -87,6 +87,8 @@ type pageCommon struct { gitInfo *source.GitInfo codeowners []string + markdownTitleInfo *MarkdownTitleInfo + // Positional navigation posNextPrev *nextPrev posNextPrevSection *nextPrev diff --git a/hugolib/page__new.go b/hugolib/page__new.go index dc3c53e49..70925e3b8 100644 --- a/hugolib/page__new.go +++ b/hugolib/page__new.go @@ -95,6 +95,12 @@ func (s *Site) doNewPageFromPageMeta(m *pageMeta, cascades *page.PageMatcherPara return nil, fmt.Errorf("failed to load CODEOWNERS: %w", err) } ps.codeowners = owners + + mt, err := s.h.markdownTitleInfoForPage(ps) + if err != nil { + return nil, fmt.Errorf("failed to load MarkdownTitle data: %w", err) + } + ps.markdownTitleInfo = mt } ps.pageMenus = &pageMenus{p: ps} diff --git a/hugolib/site.go b/hugolib/site.go index 678fea7cd..d0431d701 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -595,6 +595,10 @@ func newHugoSites( return h.loadGitInfo() }) + h.init.markdownTitleInfo = hsync.OnceMoreFunc(func(ctx context.Context) error { + return h.loadMarkdownTitleInfo() + }) + return h, nil } -- 2.39.5