pagemenus.go (4543B)
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 19 "github.com/gohugoio/hugo/common/maps" 20 "github.com/gohugoio/hugo/common/types" 21 22 "github.com/spf13/cast" 23 ) 24 25 type PageMenusProvider interface { 26 PageMenusGetter 27 MenuQueryProvider 28 } 29 30 type PageMenusGetter interface { 31 Menus() PageMenus 32 } 33 34 type MenusGetter interface { 35 Menus() Menus 36 } 37 38 type MenuQueryProvider interface { 39 HasMenuCurrent(menuID string, me *MenuEntry) bool 40 IsMenuCurrent(menuID string, inme *MenuEntry) bool 41 } 42 43 func PageMenusFromPage(p Page) (PageMenus, error) { 44 params := p.Params() 45 46 ms, ok := params["menus"] 47 if !ok { 48 ms, ok = params["menu"] 49 } 50 51 pm := PageMenus{} 52 53 if !ok { 54 return nil, nil 55 } 56 57 me := MenuEntry{Page: p, Name: p.LinkTitle(), Weight: p.Weight()} 58 59 // Could be the name of the menu to attach it to 60 mname, err := cast.ToStringE(ms) 61 62 if err == nil { 63 me.Menu = mname 64 pm[mname] = &me 65 return pm, nil 66 } 67 68 // Could be a slice of strings 69 mnames, err := cast.ToStringSliceE(ms) 70 71 if err == nil { 72 for _, mname := range mnames { 73 me.Menu = mname 74 pm[mname] = &me 75 } 76 return pm, nil 77 } 78 79 var wrapErr = func(err error) error { 80 return fmt.Errorf("unable to process menus for page %q: %w", p.Path(), err) 81 } 82 83 // Could be a structured menu entry 84 menus, err := maps.ToStringMapE(ms) 85 if err != nil { 86 return pm, wrapErr(err) 87 } 88 89 for name, menu := range menus { 90 menuEntry := MenuEntry{Page: p, Name: p.LinkTitle(), Weight: p.Weight(), Menu: name} 91 if menu != nil { 92 ime, err := maps.ToStringMapE(menu) 93 if err != nil { 94 return pm, wrapErr(err) 95 } 96 97 if err = menuEntry.MarshallMap(ime); err != nil { 98 return pm, wrapErr(err) 99 } 100 } 101 pm[name] = &menuEntry 102 } 103 104 return pm, nil 105 } 106 107 func NewMenuQueryProvider( 108 pagem PageMenusGetter, 109 sitem MenusGetter, 110 p Page) MenuQueryProvider { 111 return &pageMenus{ 112 p: p, 113 pagem: pagem, 114 sitem: sitem, 115 } 116 } 117 118 type pageMenus struct { 119 pagem PageMenusGetter 120 sitem MenusGetter 121 p Page 122 } 123 124 func (pm *pageMenus) HasMenuCurrent(menuID string, me *MenuEntry) bool { 125 if !types.IsNil(me.Page) && me.Page.IsSection() { 126 if ok, _ := me.Page.IsAncestor(pm.p); ok { 127 return true 128 } 129 } 130 131 if !me.HasChildren() { 132 return false 133 } 134 135 menus := pm.pagem.Menus() 136 137 if m, ok := menus[menuID]; ok { 138 for _, child := range me.Children { 139 if child.IsEqual(m) { 140 return true 141 } 142 if pm.HasMenuCurrent(menuID, child) { 143 return true 144 } 145 } 146 } 147 148 if pm.p == nil { 149 return false 150 } 151 152 for _, child := range me.Children { 153 if child.isSamePage(pm.p) { 154 return true 155 } 156 157 if pm.HasMenuCurrent(menuID, child) { 158 return true 159 } 160 } 161 162 return false 163 } 164 165 func (pm *pageMenus) IsMenuCurrent(menuID string, inme *MenuEntry) bool { 166 menus := pm.pagem.Menus() 167 168 if me, ok := menus[menuID]; ok { 169 if me.IsEqual(inme) { 170 return true 171 } 172 } 173 174 if pm.p == nil { 175 return false 176 } 177 178 if !inme.isSamePage(pm.p) { 179 return false 180 } 181 182 // This resource may be included in several menus. 183 // Search for it to make sure that it is in the menu with the given menuId. 184 if menu, ok := pm.sitem.Menus()[menuID]; ok { 185 for _, menuEntry := range menu { 186 if menuEntry.IsSameResource(inme) { 187 return true 188 } 189 190 descendantFound := pm.isSameAsDescendantMenu(inme, menuEntry) 191 if descendantFound { 192 return descendantFound 193 } 194 195 } 196 } 197 198 return false 199 } 200 201 func (pm *pageMenus) isSameAsDescendantMenu(inme *MenuEntry, parent *MenuEntry) bool { 202 if parent.HasChildren() { 203 for _, child := range parent.Children { 204 if child.IsSameResource(inme) { 205 return true 206 } 207 descendantFound := pm.isSameAsDescendantMenu(inme, child) 208 if descendantFound { 209 return descendantFound 210 } 211 } 212 } 213 return false 214 } 215 216 var NopPageMenus = new(nopPageMenus) 217 218 type nopPageMenus int 219 220 func (m nopPageMenus) Menus() PageMenus { 221 return PageMenus{} 222 } 223 224 func (m nopPageMenus) HasMenuCurrent(menuID string, me *MenuEntry) bool { 225 return false 226 } 227 228 func (m nopPageMenus) IsMenuCurrent(menuID string, inme *MenuEntry) bool { 229 return false 230 }