mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 15:28:54 +00:00
hugolib: Fix automatic section pages not replaced by sites.complements
When a home page or section page in language A is backed by a file and language B has no content file for that section, the automatic page for language B was not replaced by the complement from language A. This happened because: 1. The content node shifter returned auto pages (not backed by files) as exact matches without trying complement fallback. 2. findContentNodeForSiteVector immediately returned auto pages as exact matches without considering file-backed complements. 3. getPagesInSection used Get (no fallback) for IncludeSelf, preventing complement resolution for home/section pages. Fix by preferring file-backed complement pages over auto pages in the shift and lookup mechanisms, and using fallback for self-inclusion. Fixes #14540 Co-Authored-By: Joe Mooring <joe.mooring@veriphor.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -388,7 +388,7 @@ func (m *pageMap) getPagesInSection(q pageMapQueryPagesInSection) page.Pages {
|
||||
|
||||
if err == nil {
|
||||
if q.IncludeSelf {
|
||||
if n := m.treePages.Get(q.Path); n != nil {
|
||||
if n := m.treePages.GetWithFallback(q.Path); n != nil {
|
||||
if p, ok := n.(*pageState); ok && include(p) {
|
||||
pas = append(pas, p)
|
||||
}
|
||||
|
||||
@@ -223,6 +223,7 @@ func (h helperContentNode) findContentNodeForSiteVector(q sitesmatrix.Vector, fa
|
||||
var (
|
||||
best contentNodeForSite = nil
|
||||
bestDistance int
|
||||
bestWeight int
|
||||
)
|
||||
|
||||
for n := range candidates {
|
||||
@@ -233,27 +234,41 @@ func (h helperContentNode) findContentNodeForSiteVector(q sitesmatrix.Vector, fa
|
||||
if m := n.(contentNodeLookupContentNodes).lookupContentNodes(q, fallback); m != nil {
|
||||
for nn := range m {
|
||||
vec := nn.siteVector()
|
||||
if q == vec {
|
||||
// Exact match.
|
||||
var w int
|
||||
if wp, ok := nn.(contentNodeContentWeightProvider); ok {
|
||||
w = wp.contentWeight()
|
||||
}
|
||||
|
||||
if q == vec && w > 0 {
|
||||
// Exact match backed by a file.
|
||||
return nn
|
||||
}
|
||||
|
||||
distance := q.Distance(vec)
|
||||
distanceAbs := absint(distance)
|
||||
|
||||
if best == nil {
|
||||
best = nn
|
||||
bestDistance = distance
|
||||
bestWeight = w
|
||||
} else {
|
||||
distanceAbs := absint(distance)
|
||||
bestDistanceAbs := absint(bestDistance)
|
||||
if distanceAbs < bestDistanceAbs {
|
||||
// Closer is better.
|
||||
best = nn
|
||||
bestDistance = distance
|
||||
} else if distanceAbs == bestDistanceAbs && distance > 0 {
|
||||
// Positive distance is better than negative.
|
||||
if w > 0 && bestWeight == 0 {
|
||||
// Prefer file-backed pages over auto pages.
|
||||
best = nn
|
||||
bestDistance = distance
|
||||
bestWeight = w
|
||||
} else if (w > 0) == (bestWeight > 0) {
|
||||
// Both file-backed or both auto: use distance.
|
||||
if distanceAbs < bestDistanceAbs {
|
||||
best = nn
|
||||
bestDistance = distance
|
||||
bestWeight = w
|
||||
} else if distanceAbs == bestDistanceAbs && distance > 0 {
|
||||
best = nn
|
||||
bestDistance = distance
|
||||
bestWeight = w
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,17 +152,31 @@ func (s *contentNodeShifter) Insert(old, new contentNode) (contentNode, contentN
|
||||
}
|
||||
|
||||
func (s *contentNodeShifter) Shift(n contentNode, siteVector sitesmatrix.Vector, fallback bool) (contentNode, bool) {
|
||||
var exact contentNode
|
||||
switch v := n.(type) {
|
||||
case contentNodeLookupContentNode:
|
||||
if vv := v.lookupContentNode(siteVector); vv != nil {
|
||||
return vv, true
|
||||
}
|
||||
exact = v.lookupContentNode(siteVector)
|
||||
default:
|
||||
panic(fmt.Sprintf("Shift: unknown type %T for %q", n, n.Path()))
|
||||
}
|
||||
|
||||
if exact != nil {
|
||||
if !fallback {
|
||||
return exact, true
|
||||
}
|
||||
// If the exact match is backed by a file, return it directly.
|
||||
if wp, ok := exact.(contentNodeContentWeightProvider); ok && wp.contentWeight() > 0 {
|
||||
return exact, true
|
||||
}
|
||||
// The exact match is an auto page (not backed by a file).
|
||||
// Check if there's a file-backed complement that should take precedence.
|
||||
if vvv := cnh.findContentNodeForSiteVector(siteVector, fallback, contentNodeToSeq(n)); vvv != nil {
|
||||
return vvv, true
|
||||
}
|
||||
return exact, true
|
||||
}
|
||||
|
||||
if !fallback {
|
||||
// Done
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
||||
@@ -338,6 +338,22 @@ func (r *NodeShiftTree[T]) Get(s string) T {
|
||||
return t
|
||||
}
|
||||
|
||||
// GetWithFallback is like Get but uses the fallback/complement mechanism
|
||||
// when finding a node for the current site vector.
|
||||
func (r *NodeShiftTree[T]) GetWithFallback(s string) T {
|
||||
s = cleanKey(s)
|
||||
v, ok := r.tree.Get(s)
|
||||
if !ok {
|
||||
var t T
|
||||
return t
|
||||
}
|
||||
if v, ok := r.shift(v, true); ok {
|
||||
return v
|
||||
}
|
||||
var t T
|
||||
return t
|
||||
}
|
||||
|
||||
func (r *NodeShiftTree[T]) ForEeachInDimension(s string, dims sitesmatrix.Vector, d int, f func(T) bool) {
|
||||
s = cleanKey(s)
|
||||
v, ok := r.tree.Get(s)
|
||||
|
||||
@@ -1658,3 +1658,90 @@ Version: {{ .Site.Version.Name }}|
|
||||
|
||||
b.AssertFileContent("public/v1/index.html", "Edited version: v1|")
|
||||
}
|
||||
|
||||
// Issue 14540
|
||||
func TestSiteComplementsOverrideAutomaticSectionPages(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['rss', 'sitemap', 'taxonomy', 'term']
|
||||
defaultContentLanguageInSubdir = true
|
||||
|
||||
[languages.en]
|
||||
weight = 1
|
||||
|
||||
[languages.de]
|
||||
weight = 2
|
||||
|
||||
[[module.mounts]]
|
||||
source = 'content/en'
|
||||
target = 'content'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = ['en']
|
||||
[module.mounts.sites.complements]
|
||||
languages = ['de']
|
||||
|
||||
[[module.mounts]]
|
||||
source = 'content/de'
|
||||
target = 'content'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = ['de']
|
||||
-- layouts/home.html --
|
||||
<ul>
|
||||
{{ range $k, $_ := site.Pages -}}
|
||||
<li>[{{ $k }}] <a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
|
||||
{{ end -}}
|
||||
</ul>
|
||||
-- layouts/page.html --
|
||||
{{ .Title }}|
|
||||
-- layouts/section.html --
|
||||
{{ .Title }}|
|
||||
-- content/en/_index.md --
|
||||
---
|
||||
title: home en
|
||||
---
|
||||
-- content/en/s1/_index.md --
|
||||
---
|
||||
title: s1 en
|
||||
---
|
||||
-- content/en/s1/p1.md --
|
||||
---
|
||||
title: p1 en
|
||||
---
|
||||
-- content/en/s2/_index.md --
|
||||
---
|
||||
title: s2 en
|
||||
---
|
||||
-- content/en/s2/p2.md --
|
||||
---
|
||||
title: p2 en
|
||||
---
|
||||
-- content/de/s1/p1.md --
|
||||
---
|
||||
title: p1 de
|
||||
---
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/en/index.html", `
|
||||
<ul>
|
||||
<li>[0] <a href="/en/">home en</a></li>
|
||||
<li>[1] <a href="/en/s1/p1/">p1 en</a></li>
|
||||
<li>[2] <a href="/en/s2/p2/">p2 en</a></li>
|
||||
<li>[3] <a href="/en/s1/">s1 en</a></li>
|
||||
<li>[4] <a href="/en/s2/">s2 en</a></li>
|
||||
</ul>
|
||||
`)
|
||||
|
||||
b.AssertFileContent("public/de/index.html", `
|
||||
<ul>
|
||||
<li>[0] <a href="/en/">home en</a></li>
|
||||
<li>[1] <a href="/de/s1/p1/">p1 de</a></li>
|
||||
<li>[2] <a href="/en/s2/p2/">p2 en</a></li>
|
||||
<li>[3] <a href="/en/s1/">s1 en</a></li>
|
||||
<li>[4] <a href="/en/s2/">s2 en</a></li>
|
||||
</ul>
|
||||
`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user