diff --git a/docs/content/extras/menus.md b/docs/content/extras/menus.md index 957432567..fcf90096f 100644 --- a/docs/content/extras/menus.md +++ b/docs/content/extras/menus.md @@ -96,10 +96,12 @@ Here’s an example `config.toml`: pre = "" weight = -110 identifier = "about" + url = "/about/" [[menu.main]] name = "getting started" pre = "" weight = -100 + url = "/getting-started/" And the equivalent example `config.yaml`: @@ -110,11 +112,16 @@ And the equivalent example `config.yaml`: Pre: "" Weight: -110 Identifier: "about" + Url: "/about/" - Name: "getting started" Pre: "" Weight: -100 + Url: "/getting-started/" --- + +**NOTE:** The urls must be relative to the context root. If the `BaseUrl` is `http://example.com/mysite/`, then the urls in the menu must not include the context root `mysite`. + ## Nesting All nesting of content is done via the `parent` field. diff --git a/helpers/url.go b/helpers/url.go index 46bc951f4..1b7608178 100644 --- a/helpers/url.go +++ b/helpers/url.go @@ -78,6 +78,25 @@ func MakePermalink(host, plink string) *url.URL { return base } +// AddContextRoot adds the context root to an URL if it's not already set. +// For relative URL entries on sites with a base url with a context root set (i.e. http://example.com/mysite), +// relative URLs must not include the context root if canonifyUrls is enabled. But if it's disabled, it must be set. +func AddContextRoot(baseUrl, relativePath string) string { + + url, err := url.Parse(baseUrl) + if err != nil { + panic(err) + } + + newPath := path.Join(url.Path, relativePath) + + // path strips traling slash + if strings.HasSuffix(relativePath, "/") { + newPath += "/" + } + return newPath +} + func UrlPrep(ugly bool, in string) string { if ugly { x := Uglify(SanitizeUrl(in)) diff --git a/helpers/url_test.go b/helpers/url_test.go index 1d5c770ea..3df1a05c2 100644 --- a/helpers/url_test.go +++ b/helpers/url_test.go @@ -68,6 +68,29 @@ func TestUrlPrep(t *testing.T) { } +func TestAddContextRoot(t *testing.T) { + tests := []struct { + baseUrl string + url string + expected string + }{ + {"http://example.com/sub/", "/foo", "/sub/foo"}, + {"http://example.com/sub/", "/foo/index.html", "/sub/foo/index.html"}, + {"http://example.com/sub1/sub2", "/foo", "/sub1/sub2/foo"}, + {"http://example.com", "/foo", "/foo"}, + // cannot guess that the context root is already added int the example below + {"http://example.com/sub/", "/sub/foo", "/sub/sub/foo"}, + {"http://example.com/тря", "/трям/", "/тря/трям/"}, + } + + for _, test := range tests { + output := AddContextRoot(test.baseUrl, test.url) + if output != test.expected { + t.Errorf("Expected %#v, got %#v\n", test.expected, output) + } + } +} + func TestPretty(t *testing.T) { assert.Equal(t, PrettifyUrlPath("/section/name.html"), "/section/name/index.html") assert.Equal(t, PrettifyUrlPath("/section/sub/name.html"), "/section/sub/name/index.html") diff --git a/hugolib/menu_test.go b/hugolib/menu_test.go index afe881a94..667105f50 100644 --- a/hugolib/menu_test.go +++ b/hugolib/menu_test.go @@ -265,18 +265,27 @@ func TestPageMenu(t *testing.T) { // issue #719 func TestMenuWithUnicodeUrls(t *testing.T) { for _, uglyUrls := range []bool{true, false} { - doTestMenuWithUnicodeUrls(t, uglyUrls) + for _, canonifyUrls := range []bool{true, false} { + doTestMenuWithUnicodeUrls(t, canonifyUrls, uglyUrls) + } } } -func doTestMenuWithUnicodeUrls(t *testing.T, uglyUrls bool) { +func doTestMenuWithUnicodeUrls(t *testing.T, canonifyUrls, uglyUrls bool) { + viper.Set("CanonifyUrls", canonifyUrls) viper.Set("UglyUrls", uglyUrls) + ts := setupMenuTests(t, MENU_PAGE_SOURCES) defer resetMenuTestState(ts) unicodeRussian := ts.findTestMenuEntryById("unicode", "unicode-russian") - expectedBase := "http://foo.local/zoo/%D0%BD%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D0%B8-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B0" + expectedBase := "/%D0%BD%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D0%B8-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B0" + + if !canonifyUrls { + expectedBase = "/zoo" + expectedBase + } + var expected string if uglyUrls { expected = expectedBase + ".html" @@ -288,6 +297,7 @@ func doTestMenuWithUnicodeUrls(t *testing.T, uglyUrls bool) { } func TestTaxonomyNodeMenu(t *testing.T) { + viper.Set("CanonifyUrls", true) ts := setupMenuTests(t, MENU_PAGE_SOURCES) defer resetMenuTestState(ts) @@ -333,7 +343,7 @@ func TestHomeNodeMenu(t *testing.T) { defer resetMenuTestState(ts) home := ts.site.newHomeNode() - homeMenuEntry := &MenuEntry{Name: home.Title, Url: string(home.Permalink)} + homeMenuEntry := &MenuEntry{Name: home.Title, Url: home.Url} for i, this := range []struct { menu string diff --git a/hugolib/node.go b/hugolib/node.go index 0c3ed9ce6..ccf3e8822 100644 --- a/hugolib/node.go +++ b/hugolib/node.go @@ -38,7 +38,7 @@ func (n *Node) Now() time.Time { func (n *Node) HasMenuCurrent(menuId string, inme *MenuEntry) bool { if inme.HasChildren() { - me := MenuEntry{Name: n.Title, Url: string(n.Permalink)} + me := MenuEntry{Name: n.Title, Url: n.Url} for _, child := range inme.Children { if me.IsSameResource(child) { @@ -52,8 +52,7 @@ func (n *Node) HasMenuCurrent(menuId string, inme *MenuEntry) bool { func (n *Node) IsMenuCurrent(menuId string, inme *MenuEntry) bool { - me := MenuEntry{Name: n.Title, Url: string(n.Permalink)} - + me := MenuEntry{Name: n.Title, Url: n.Url} if !me.IsSameResource(inme) { return false } diff --git a/hugolib/page.go b/hugolib/page.go index c30728c03..395462d84 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -397,6 +397,16 @@ func (p *Page) RelPermalink() (string, error) { return "", err } + if viper.GetBool("CanonifyUrls") { + // replacements for relpermalink with baseUrl on the form http://myhost.com/sub/ will fail later on + // have to return the Url relative from baseUrl + relpath, err := helpers.GetRelativePath(link.String(), string(p.Site.BaseUrl)) + if err != nil { + return "", err + } + return "/" + filepath.ToSlash(relpath), nil + } + link.Scheme = "" link.Host = "" link.User = nil @@ -549,7 +559,7 @@ func (page *Page) Menus() PageMenus { ret := PageMenus{} if ms, ok := page.Params["menu"]; ok { - link, _ := page.Permalink() + link, _ := page.RelPermalink() me := MenuEntry{Name: page.LinkTitle(), Weight: page.Weight, Url: link} diff --git a/hugolib/page_permalink_test.go b/hugolib/page_permalink_test.go index 97b3d24c6..b73e08721 100644 --- a/hugolib/page_permalink_test.go +++ b/hugolib/page_permalink_test.go @@ -11,34 +11,39 @@ import ( func TestPermalink(t *testing.T) { tests := []struct { - file string - dir string - base template.URL - slug string - url string - uglyurls bool - expectedAbs string - expectedRel string + file string + dir string + base template.URL + slug string + url string + uglyUrls bool + canonifyUrls bool + expectedAbs string + expectedRel string }{ - {"x/y/z/boofar.md", "x/y/z", "", "", "", false, "/x/y/z/boofar/", "/x/y/z/boofar/"}, - {"x/y/z/boofar.md", "x/y/z/", "", "", "", false, "/x/y/z/boofar/", "/x/y/z/boofar/"}, - {"x/y/z/boofar.md", "x/y/z/", "", "boofar", "", false, "/x/y/z/boofar/", "/x/y/z/boofar/"}, - {"x/y/z/boofar.md", "x/y/z", "http://barnew/", "", "", false, "http://barnew/x/y/z/boofar/", "/x/y/z/boofar/"}, - {"x/y/z/boofar.md", "x/y/z/", "http://barnew/", "boofar", "", false, "http://barnew/x/y/z/boofar/", "/x/y/z/boofar/"}, - {"x/y/z/boofar.md", "x/y/z", "", "", "", true, "/x/y/z/boofar.html", "/x/y/z/boofar.html"}, - {"x/y/z/boofar.md", "x/y/z/", "", "", "", true, "/x/y/z/boofar.html", "/x/y/z/boofar.html"}, - {"x/y/z/boofar.md", "x/y/z/", "", "boofar", "", true, "/x/y/z/boofar.html", "/x/y/z/boofar.html"}, - {"x/y/z/boofar.md", "x/y/z", "http://barnew/", "", "", true, "http://barnew/x/y/z/boofar.html", "/x/y/z/boofar.html"}, - {"x/y/z/boofar.md", "x/y/z/", "http://barnew/", "boofar", "", true, "http://barnew/x/y/z/boofar.html", "/x/y/z/boofar.html"}, + {"x/y/z/boofar.md", "x/y/z", "", "", "", false, false, "/x/y/z/boofar/", "/x/y/z/boofar/"}, + {"x/y/z/boofar.md", "x/y/z/", "", "", "", false, false, "/x/y/z/boofar/", "/x/y/z/boofar/"}, + {"x/y/z/boofar.md", "x/y/z/", "", "boofar", "", false, false, "/x/y/z/boofar/", "/x/y/z/boofar/"}, + {"x/y/z/boofar.md", "x/y/z", "http://barnew/", "", "", false, false, "http://barnew/x/y/z/boofar/", "/x/y/z/boofar/"}, + {"x/y/z/boofar.md", "x/y/z/", "http://barnew/", "boofar", "", false, false, "http://barnew/x/y/z/boofar/", "/x/y/z/boofar/"}, + {"x/y/z/boofar.md", "x/y/z", "", "", "", true, false, "/x/y/z/boofar.html", "/x/y/z/boofar.html"}, + {"x/y/z/boofar.md", "x/y/z/", "", "", "", true, false, "/x/y/z/boofar.html", "/x/y/z/boofar.html"}, + {"x/y/z/boofar.md", "x/y/z/", "", "boofar", "", true, false, "/x/y/z/boofar.html", "/x/y/z/boofar.html"}, + {"x/y/z/boofar.md", "x/y/z", "http://barnew/", "", "", true, false, "http://barnew/x/y/z/boofar.html", "/x/y/z/boofar.html"}, + {"x/y/z/boofar.md", "x/y/z/", "http://barnew/", "boofar", "", true, false, "http://barnew/x/y/z/boofar.html", "/x/y/z/boofar.html"}, + {"x/y/z/boofar.md", "x/y/z/", "http://barnew/boo/", "boofar", "", true, false, "http://barnew/boo/x/y/z/boofar.html", "/boo/x/y/z/boofar.html"}, + {"x/y/z/boofar.md", "x/y/z/", "http://barnew/boo/", "boofar", "", true, true, "http://barnew/boo/x/y/z/boofar.html", "/x/y/z/boofar.html"}, + {"x/y/z/boofar.md", "x/y/z/", "http://barnew/boo", "boofar", "", true, true, "http://barnew/boo/x/y/z/boofar.html", "/x/y/z/boofar.html"}, // test url overrides - {"x/y/z/boofar.md", "x/y/z", "", "", "/z/y/q/", false, "/z/y/q/", "/z/y/q/"}, + {"x/y/z/boofar.md", "x/y/z", "", "", "/z/y/q/", false, false, "/z/y/q/", "/z/y/q/"}, } viper.Set("DefaultExtension", "html") for i, test := range tests { - viper.Set("uglyurls", test.uglyurls) + viper.Set("uglyurls", test.uglyUrls) + viper.Set("canonifyurls", test.canonifyUrls) p := &Page{ Node: Node{ UrlPath: UrlPath{ @@ -75,7 +80,7 @@ func TestPermalink(t *testing.T) { expected = test.expectedRel if u != expected { - t.Errorf("Test %d: Expected abs url: %s, got: %s", i, expected, u) + t.Errorf("Test %d: Expected rel url: %s, got: %s", i, expected, u) } } } diff --git a/hugolib/site.go b/hugolib/site.go index 413e9f4e2..95a978f93 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -106,6 +106,7 @@ type SiteInfo struct { Permalinks PermalinkOverrides Params map[string]interface{} BuildDrafts bool + canonifyUrls bool } // SiteSocial is a place to put social details on a site level. These are the @@ -362,6 +363,7 @@ func (s *Site) initializeSiteInfo() { Copyright: viper.GetString("copyright"), DisqusShortname: viper.GetString("DisqusShortname"), BuildDrafts: viper.GetBool("BuildDrafts"), + canonifyUrls: viper.GetBool("CanonifyUrls"), Pages: &s.Pages, Recent: &s.Pages, Menus: &s.Menus, @@ -608,10 +610,16 @@ func (s *Site) getMenusFromConfig() Menus { } menuEntry.MarshallMap(ime) + if strings.HasPrefix(menuEntry.Url, "/") { - // make it absolute so it matches the nodes - menuEntry.Url = s.permalinkStr(menuEntry.Url) + // make it match the nodes + menuEntryUrl := menuEntry.Url + if !s.Info.canonifyUrls { + menuEntryUrl = helpers.AddContextRoot(string(s.Info.BaseUrl), menuEntryUrl) + } + menuEntry.Url = s.prepUrl(menuEntryUrl) } + if ret[name] == nil { ret[name] = &Menu{} }