hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

menu.go (7324B)

    1 // Copyright 2019 The Hugo Authors. All rights reserved.
    2 //
    3 // Licensed under the Apache License, Version 2.0 (the "License");
    4 // you may not use this file except in compliance with the License.
    5 // You may obtain a copy of the License at
    6 // http://www.apache.org/licenses/LICENSE-2.0
    7 //
    8 // Unless required by applicable law or agreed to in writing, software
    9 // distributed under the License is distributed on an "AS IS" BASIS,
   10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   11 // See the License for the specific language governing permissions and
   12 // limitations under the License.
   13 
   14 package navigation
   15 
   16 import (
   17 	"fmt"
   18 	"html/template"
   19 	"sort"
   20 	"strings"
   21 
   22 	"github.com/gohugoio/hugo/common/maps"
   23 	"github.com/gohugoio/hugo/common/types"
   24 	"github.com/gohugoio/hugo/compare"
   25 
   26 	"github.com/spf13/cast"
   27 )
   28 
   29 var smc = newMenuCache()
   30 
   31 // MenuEntry represents a menu item defined in either Page front matter
   32 // or in the site config.
   33 type MenuEntry struct {
   34 	// The URL value from front matter / config.
   35 	ConfiguredURL string
   36 
   37 	// The Page connected to this menu entry.
   38 	Page Page
   39 
   40 	// The path to the page, only relevant for menus defined in site config.
   41 	PageRef string
   42 
   43 	// The name of the menu entry.
   44 	Name string
   45 
   46 	// The menu containing this menu entry.
   47 	Menu string
   48 
   49 	// Used to identify this menu entry.
   50 	Identifier string
   51 
   52 	title string
   53 
   54 	// If set, will be rendered before this menu entry.
   55 	Pre template.HTML
   56 
   57 	// If set, will be rendered after this menu entry.
   58 	Post template.HTML
   59 
   60 	// The weight of this menu entry, used for sorting.
   61 	// Set to a non-zero value, negative or positive.
   62 	Weight int
   63 
   64 	// Identifier of the parent menu entry.
   65 	Parent string
   66 
   67 	// Child entries.
   68 	Children Menu
   69 
   70 	// User defined params.
   71 	Params maps.Params
   72 }
   73 
   74 func (m *MenuEntry) URL() string {
   75 
   76 	// Check page first.
   77 	// In Hugo 0.86.0 we added `pageRef`,
   78 	// a way to connect menu items in site config to pages.
   79 	// This means that you now can have both a Page
   80 	// and a configured URL.
   81 	// Having the configured URL as a fallback if the Page isn't found
   82 	// is obviously more useful, especially in multilingual sites.
   83 	if !types.IsNil(m.Page) {
   84 		return m.Page.RelPermalink()
   85 	}
   86 
   87 	return m.ConfiguredURL
   88 }
   89 
   90 // A narrow version of page.Page.
   91 type Page interface {
   92 	LinkTitle() string
   93 	RelPermalink() string
   94 	Path() string
   95 	Section() string
   96 	Weight() int
   97 	IsPage() bool
   98 	IsSection() bool
   99 	IsAncestor(other any) (bool, error)
  100 	Params() maps.Params
  101 }
  102 
  103 // Menu is a collection of menu entries.
  104 type Menu []*MenuEntry
  105 
  106 // Menus is a dictionary of menus.
  107 type Menus map[string]Menu
  108 
  109 // PageMenus is a dictionary of menus defined in the Pages.
  110 type PageMenus map[string]*MenuEntry
  111 
  112 // HasChildren returns whether this menu item has any children.
  113 func (m *MenuEntry) HasChildren() bool {
  114 	return m.Children != nil
  115 }
  116 
  117 // KeyName returns the key used to identify this menu entry.
  118 func (m *MenuEntry) KeyName() string {
  119 	if m.Identifier != "" {
  120 		return m.Identifier
  121 	}
  122 	return m.Name
  123 }
  124 
  125 func (m *MenuEntry) hopefullyUniqueID() string {
  126 	if m.Identifier != "" {
  127 		return m.Identifier
  128 	} else if m.URL() != "" {
  129 		return m.URL()
  130 	} else {
  131 		return m.Name
  132 	}
  133 }
  134 
  135 // IsEqual returns whether the two menu entries represents the same menu entry.
  136 func (m *MenuEntry) IsEqual(inme *MenuEntry) bool {
  137 	return m.hopefullyUniqueID() == inme.hopefullyUniqueID() && m.Parent == inme.Parent
  138 }
  139 
  140 // IsSameResource returns whether the two menu entries points to the same
  141 // resource (URL).
  142 func (m *MenuEntry) IsSameResource(inme *MenuEntry) bool {
  143 	if m.isSamePage(inme.Page) {
  144 		return m.Page == inme.Page
  145 	}
  146 	murl, inmeurl := m.URL(), inme.URL()
  147 	return murl != "" && inmeurl != "" && murl == inmeurl
  148 }
  149 
  150 func (m *MenuEntry) isSamePage(p Page) bool {
  151 	if !types.IsNil(m.Page) && !types.IsNil(p) {
  152 		return m.Page == p
  153 	}
  154 	return false
  155 }
  156 
  157 // For internal use.
  158 func (m *MenuEntry) MarshallMap(ime map[string]any) error {
  159 	var err error
  160 	for k, v := range ime {
  161 		loki := strings.ToLower(k)
  162 		switch loki {
  163 		case "url":
  164 			m.ConfiguredURL = cast.ToString(v)
  165 		case "pageref":
  166 			m.PageRef = cast.ToString(v)
  167 		case "weight":
  168 			m.Weight = cast.ToInt(v)
  169 		case "name":
  170 			m.Name = cast.ToString(v)
  171 		case "title":
  172 			m.title = cast.ToString(v)
  173 		case "pre":
  174 			m.Pre = template.HTML(cast.ToString(v))
  175 		case "post":
  176 			m.Post = template.HTML(cast.ToString(v))
  177 		case "identifier":
  178 			m.Identifier = cast.ToString(v)
  179 		case "parent":
  180 			m.Parent = cast.ToString(v)
  181 		case "params":
  182 			var ok bool
  183 			m.Params, ok = maps.ToParamsAndPrepare(v)
  184 			if !ok {
  185 				err = fmt.Errorf("cannot convert %T to Params", v)
  186 			}
  187 		}
  188 	}
  189 
  190 	if err != nil {
  191 		return fmt.Errorf("failed to marshal menu entry %q: %w", m.KeyName(), err)
  192 	}
  193 
  194 	return nil
  195 }
  196 
  197 // This is for internal use only.
  198 func (m Menu) Add(me *MenuEntry) Menu {
  199 	m = append(m, me)
  200 	// TODO(bep)
  201 	m.Sort()
  202 	return m
  203 }
  204 
  205 /*
  206  * Implementation of a custom sorter for Menu
  207  */
  208 
  209 // A type to implement the sort interface for Menu
  210 type menuSorter struct {
  211 	menu Menu
  212 	by   menuEntryBy
  213 }
  214 
  215 // Closure used in the Sort.Less method.
  216 type menuEntryBy func(m1, m2 *MenuEntry) bool
  217 
  218 func (by menuEntryBy) Sort(menu Menu) {
  219 	ms := &menuSorter{
  220 		menu: menu,
  221 		by:   by, // The Sort method's receiver is the function (closure) that defines the sort order.
  222 	}
  223 	sort.Stable(ms)
  224 }
  225 
  226 var defaultMenuEntrySort = func(m1, m2 *MenuEntry) bool {
  227 	if m1.Weight == m2.Weight {
  228 		c := compare.Strings(m1.Name, m2.Name)
  229 		if c == 0 {
  230 			return m1.Identifier < m2.Identifier
  231 		}
  232 		return c < 0
  233 	}
  234 
  235 	if m2.Weight == 0 {
  236 		return true
  237 	}
  238 
  239 	if m1.Weight == 0 {
  240 		return false
  241 	}
  242 
  243 	return m1.Weight < m2.Weight
  244 }
  245 
  246 func (ms *menuSorter) Len() int      { return len(ms.menu) }
  247 func (ms *menuSorter) Swap(i, j int) { ms.menu[i], ms.menu[j] = ms.menu[j], ms.menu[i] }
  248 
  249 // Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
  250 func (ms *menuSorter) Less(i, j int) bool { return ms.by(ms.menu[i], ms.menu[j]) }
  251 
  252 // Sort sorts the menu by weight, name and then by identifier.
  253 func (m Menu) Sort() Menu {
  254 	menuEntryBy(defaultMenuEntrySort).Sort(m)
  255 	return m
  256 }
  257 
  258 // Limit limits the returned menu to n entries.
  259 func (m Menu) Limit(n int) Menu {
  260 	if len(m) > n {
  261 		return m[0:n]
  262 	}
  263 	return m
  264 }
  265 
  266 // ByWeight sorts the menu by the weight defined in the menu configuration.
  267 func (m Menu) ByWeight() Menu {
  268 	const key = "menuSort.ByWeight"
  269 	menus, _ := smc.get(key, menuEntryBy(defaultMenuEntrySort).Sort, m)
  270 
  271 	return menus
  272 }
  273 
  274 // ByName sorts the menu by the name defined in the menu configuration.
  275 func (m Menu) ByName() Menu {
  276 	const key = "menuSort.ByName"
  277 	title := func(m1, m2 *MenuEntry) bool {
  278 		return compare.LessStrings(m1.Name, m2.Name)
  279 	}
  280 
  281 	menus, _ := smc.get(key, menuEntryBy(title).Sort, m)
  282 
  283 	return menus
  284 }
  285 
  286 // Reverse reverses the order of the menu entries.
  287 func (m Menu) Reverse() Menu {
  288 	const key = "menuSort.Reverse"
  289 	reverseFunc := func(menu Menu) {
  290 		for i, j := 0, len(menu)-1; i < j; i, j = i+1, j-1 {
  291 			menu[i], menu[j] = menu[j], menu[i]
  292 		}
  293 	}
  294 	menus, _ := smc.get(key, reverseFunc, m)
  295 
  296 	return menus
  297 }
  298 
  299 // Clone clones the menu entries.
  300 // This is for internal use only.
  301 func (m Menu) Clone() Menu {
  302 	return append(Menu(nil), m...)
  303 }
  304 
  305 func (m *MenuEntry) Title() string {
  306 	if m.title != "" {
  307 		return m.title
  308 	}
  309 
  310 	if m.Page != nil {
  311 		return m.Page.LinkTitle()
  312 	}
  313 
  314 	return ""
  315 }