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 }