summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/content/en/functions/RenderString.md37
-rw-r--r--docs/content/en/getting-started/configuration-markup.md59
-rw-r--r--helpers/content.go4
-rw-r--r--hugolib/content_render_hooks_test.go244
-rw-r--r--hugolib/filesystems/basefs.go49
-rw-r--r--hugolib/hugo_modules_test.go3
-rw-r--r--hugolib/hugo_sites.go39
-rw-r--r--hugolib/hugo_sites_build.go2
-rw-r--r--hugolib/hugo_sites_build_test.go16
-rw-r--r--hugolib/page.go184
-rw-r--r--hugolib/page__content.go3
-rw-r--r--hugolib/page__meta.go36
-rw-r--r--hugolib/page__new.go24
-rw-r--r--hugolib/page__output.go69
-rw-r--r--hugolib/page__per_output.go303
-rw-r--r--hugolib/page_test.go7
-rw-r--r--hugolib/page_unwrap_test.go1
-rw-r--r--hugolib/pagebundler_test.go1
-rw-r--r--hugolib/pagecollections.go10
-rw-r--r--hugolib/shortcode.go19
-rw-r--r--hugolib/shortcode_page.go19
-rw-r--r--hugolib/shortcode_test.go7
-rw-r--r--hugolib/site.go120
-rw-r--r--hugolib/site_benchmark_new_test.go30
-rw-r--r--hugolib/template_test.go87
-rw-r--r--hugolib/testhelpers_test.go9
-rw-r--r--identity/identity.go131
-rw-r--r--identity/identity_test.go42
-rw-r--r--markup/asciidoc/convert.go5
-rw-r--r--markup/blackfriday/convert.go5
-rw-r--r--markup/converter/converter.go13
-rw-r--r--markup/converter/hooks/hooks.go57
-rw-r--r--markup/goldmark/convert.go112
-rw-r--r--markup/goldmark/convert_test.go15
-rw-r--r--markup/goldmark/render_link.go208
-rw-r--r--markup/mmark/convert.go5
-rw-r--r--markup/org/convert.go6
-rw-r--r--markup/pandoc/convert.go5
-rw-r--r--markup/rst/convert.go5
-rw-r--r--output/layout.go34
-rw-r--r--output/layout_test.go3
-rw-r--r--resources/page/page.go5
-rw-r--r--resources/page/page_nop.go8
-rw-r--r--resources/page/testhelpers_test.go6
-rw-r--r--scripts/fork_go_templates/main.go1
-rw-r--r--tpl/internal/go_templates/texttemplate/exec.go2
-rw-r--r--tpl/internal/go_templates/texttemplate/hugo_template.go104
-rw-r--r--tpl/internal/go_templates/texttemplate/hugo_template_test.go22
-rw-r--r--tpl/partials/partials.go6
-rw-r--r--tpl/template.go39
-rw-r--r--tpl/template_info.go52
-rw-r--r--tpl/tplimpl/shortcodes.go4
-rw-r--r--tpl/tplimpl/shortcodes_test.go8
-rw-r--r--tpl/tplimpl/template.go175
-rw-r--r--tpl/tplimpl/templateProvider.go9
-rw-r--r--tpl/tplimpl/template_ast_transformers.go166
-rw-r--r--tpl/tplimpl/template_ast_transformers_test.go66
-rw-r--r--tpl/tplimpl/template_funcs.go24
-rw-r--r--tpl/tplimpl/template_info_test.go7
59 files changed, 2212 insertions, 520 deletions
diff --git a/docs/content/en/functions/RenderString.md b/docs/content/en/functions/RenderString.md
new file mode 100644
index 000000000..61f5d6417
--- /dev/null
+++ b/docs/content/en/functions/RenderString.md
@@ -0,0 +1,37 @@
+---
+title: .RenderString
+description: "Renders markup to HTML."
+godocref:
+date: 2019-12-18
+categories: [functions]
+menu:
+ docs:
+ parent: "functions"
+keywords: [markdown,goldmark,render]
+signature: [".RenderString MARKUP"]
+---
+
+{{< new-in "0.62.0" >}}
+
+`.RenderString` is a method on `Page` that renders some markup to HTML using the content renderer defined for that page (if not set in the options).
+
+The method takes an optional map argument with these options:
+
+display ("inline")
+: `inline` or `block`. If `inline` (default), surrounding ´<p></p>` on short snippets will be trimmed.
+
+markup (defaults to the Page's markup)
+: See identifiers in [List of content formats](/content-management/formats/#list-of-content-formats).
+
+Some examples:
+
+```go-html-template
+{{ $optBlock := dict "display" "block" }}
+{{ $optOrg := dict "markup" "org" }}
+{{ "**Bold Markdown**" | $p.RenderString }}
+{{ "**Bold Block Markdown**" | $p.RenderString $optBlock }}
+{{ "/italic org mode/" | $p.RenderString $optOrg }}:REND
+```
+
+
+**Note** that this method is more powerful than the similar [markdownify](functions/markdownify/) function as it also supports [Render Hooks](/getting-started/configuration-markup/#markdown-render-hooks) and it has options to render other markup formats. \ No newline at end of file
diff --git a/docs/content/en/getting-started/configuration-markup.md b/docs/content/en/getting-started/configuration-markup.md
index ff0095024..f254b9012 100644
--- a/docs/content/en/getting-started/configuration-markup.md
+++ b/docs/content/en/getting-started/configuration-markup.md
@@ -74,3 +74,62 @@ endLevel
ordered
: Whether or not to generate an ordered list instead of an unordered list.
+
+
+## Markdown Render Hooks
+
+{{< new-in "0.62.0" >}}
+
+Note that this is only supported with the [Goldmark](#goldmark) renderer.
+
+These Render Hooks allow custom templates to render links and images from markdown.
+
+You can do this by creating templates with base names `render-link` and/or `render-image` inside `layouts/_default`.
+
+You can define [Output Format](/templates/output-formats) specific templates if needed.[^1] Your `layouts` folder may then look like this:
+
+```bash
+layouts
+└── _default
+ └── markup
+ ├── render-image.html
+ ├── render-image.rss.xml
+ └── render-link.html
+```
+
+Some use cases for the above:
+
+* Resolve link references using `.GetPage`. This would make links more portable as you could translate `./my-post.md` (and similar constructs that would work on GitHub) into `/blog/2019/01/01/my-post/` etc.
+* Add `target=blank` to external links.
+* Resolve (look in the page bundle, inside `/assets` etc.) and [transform](/content-management/image-processing) images.
+
+
+[^1]: It's currently only possible to have one set of render hook templates, e.g. not per `Type` or `Section`. We may consider that in a future version.
+
+### Render Hook Templates
+
+Both `render-link` and `render-image` templates will receive this context:
+
+Page
+: The [Page](/variables/page/) being rendered.
+
+Destination
+: The URL.
+
+Title
+: The title attribute.
+
+Text
+: The link text.
+
+A Markdown example for a inline-style link with title:
+
+```md
+[Text](https://www.gohugo.io "Title")
+```
+
+A very simple template example given the above:
+
+{{< code file="layouts/_default/render-link.html" >}}
+<a href="{{ .Destination | safeURL }}"{{ with .Title}}title="{{ . }}"{{ end }}>{{ .Text }}{{ with .Page }} (in page {{ .Title }}){{ end }}"</a>
+{{< /code >}}
diff --git a/helpers/content.go b/helpers/content.go
index 4dc4cd413..1c780fefe 100644
--- a/helpers/content.go
+++ b/helpers/content.go
@@ -25,13 +25,14 @@ import (
"github.com/gohugoio/hugo/common/loggers"
+ "github.com/spf13/afero"
+
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup"
bp "github.com/gohugoio/hugo/bufferpool"
"github.com/gohugoio/hugo/config"
- "github.com/spf13/afero"
"strings"
)
@@ -78,6 +79,7 @@ func NewContentSpec(cfg config.Provider, logger *loggers.Logger, contentFs afero
ContentFs: contentFs,
Logger: logger,
})
+
if err != nil {
return nil, err
}
diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go
new file mode 100644
index 000000000..aa697220d
--- /dev/null
+++ b/hugolib/content_render_hooks_test.go
@@ -0,0 +1,244 @@
+// Copyright 2019 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless requiredF by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugolib
+
+import "testing"
+
+func TestRenderHooks(t *testing.T) {
+ config := `
+baseURL="https://example.org"
+workingDir="/mywork"
+`
+ b := newTestSitesBuilder(t).WithWorkingDir("/mywork").WithConfigFile("toml", config).Running()
+ b.WithTemplatesAdded("_default/single.html", `{{ .Content }}`)
+ b.WithTemplatesAdded("shortcodes/myshortcode1.html", `{{ partial "mypartial1" }}`)
+ b.WithTemplatesAdded("shortcodes/myshortcode2.html", `{{ partial "mypartial2" }}`)
+ b.WithTemplatesAdded("shortcodes/myshortcode3.html", `SHORT3|`)
+ b.WithTemplatesAdded("shortcodes/myshortcode4.html", `
+<div class="foo">
+{{ .Inner | markdownify }}
+</div>
+`)
+ b.WithTemplatesAdded("shortcodes/myshortcode5.html", `
+Inner Inline: {{ .Inner | .Page.RenderString }}
+Inner Block: {{ .Inner | .Page.RenderString (dict "display" "block" ) }}
+`)
+
+ b.WithTemplatesAdded("shortcodes/myshortcode6.html", `.Render: {{ .Page.Render "myrender" }}`)
+ b.WithTemplatesAdded("partials/mypartial1.html", `PARTIAL1`)
+ b.WithTemplatesAdded("partials/mypartial2.html", `PARTIAL2 {{ partial "mypartial3.html" }}`)
+ b.WithTemplatesAdded("partials/mypartial3.html", `PARTIAL3`)
+ b.WithTemplatesAdded("partials/mypartial4.html", `PARTIAL4`)
+ b.WithTemplatesAdded("customview/myrender.html", `myrender: {{ .Title }}|P4: {{ partial "mypartial4" }}`)
+ b.WithTemplatesAdded("_default/_markup/render-link.html", `{{ with .Page }}{{ .Title }}{{ end }}|{{ .Destination | safeURL }}|Title: {{ .Title | safeHTML }}|Text: {{ .Text | safeHTML }}|END`)
+ b.WithTemplatesAdded("docs/_markup/render-link.html", `Link docs section: {{ .Text | safeHTML }}|END`)
+ b.WithTemplatesAdded("_default/_markup/render-image.html", `IMAGE: {{ .Page.Title }}||{{ .Destination | safeURL }}|Title: {{ .Title | safeHTML }}|Text: {{ .Text | safeHTML }}|END`)
+
+ b.WithContent("customview/p1.md", `---
+title: Custom View
+---
+
+{{< myshortcode6 >}}
+
+ `, "blog/p1.md", `---
+title: Cool Page
+---
+
+[First Link](https://www.google.com "Google's Homepage")
+
+{{< myshortcode3 >}}
+
+[Second Link](https://www.google.com "Google's Homepage")
+
+Image:
+
+![Drag Racing](/images/Dragster.jpg "image title")
+
+
+`, "blog/p2.md", `---
+title: Cool Page2
+layout: mylayout
+---
+
+{{< myshortcode1 >}}
+
+[Some Text](https://www.google.com "Google's Homepage")
+
+
+
+`, "blog/p3.md", `---
+title: Cool Page3
+---
+
+{{< myshortcode2 >}}
+
+
+`, "docs/docs1.md", `---
+title: Docs 1
+---
+
+
+[Docs 1](https://www.google.com "Google's Homepage")
+
+
+`, "blog/p4.md", `---
+title: Cool Page With Image
+---
+
+Image:
+
+![Drag Racing](/images/Dragster.jpg "image title")
+
+
+`, "blog/p5.md", `---
+title: Cool Page With Markdownify
+---
+
+{{< myshortcode4 >}}
+Inner Link: [Inner Link](https://www.google.com "Google's Homepage")
+{{< /myshortcode4 >}}
+
+`, "blog/p6.md", `---
+title: With RenderString
+---
+
+{{< myshortcode5 >}}Inner Link: [Inner Link](https://www.gohugo.io "Hugo's Homepage"){{< /myshortcode5 >}}
+
+`)
+ b.Build(BuildCfg{})
+ b.AssertFileContent("public/blog/p1/index.html", `
+<p>Cool Page|https://www.google.com|Title: Google's Homepage|Text: First Link|END</p>
+Text: Second
+SHORT3|
+<p>IMAGE: Cool Page||/images/Dragster.jpg|Title: image title|Text: Drag Racing|END</p>
+`)
+
+ b.AssertFileContent("public/customview/p1/index.html", `.Render: myrender: Custom View|P4: PARTIAL4`)
+ b.AssertFileContent("public/blog/p2/index.html", `PARTIAL`)
+ b.AssertFileContent("public/blog/p3/index.html", `PARTIAL3`)
+ // We may add type template support later, keep this for then. b.AssertFileContent("public/docs/docs1/index.html", `Link docs section: Docs 1|END`)
+ b.AssertFileContent("public/blog/p4/index.html", `<p>IMAGE: Cool Page With Image||/images/Dragster.jpg|Title: image title|Text: Drag Racing|END</p>`)
+ // The regular markdownify func currently gets regular links.
+ b.AssertFileContent("public/blog/p5/index.html", "Inner Link: <a href=\"https://www.google.com\" title=\"Google's Homepage\">Inner Link</a>\n</div>")
+
+ b.AssertFileContent("public/blog/p6/index.html",
+ "Inner Inline: Inner Link: With RenderString|https://www.gohugo.io|Title: Hugo's Homepage|Text: Inner Link|END",
+ "Inner Block: <p>Inner Link: With RenderString|https://www.gohugo.io|Title: Hugo's Homepage|Text: Inner Link|END</p>",
+ )
+
+ b.EditFiles(
+ "layouts/_default/_markup/render-link.html", `EDITED: {{ .Destination | safeURL }}|`,
+ "layouts/_default/_markup/render-image.html", `IMAGE EDITED: {{ .Destination | safeURL }}|`,
+ "layouts/docs/_markup/render-link.html", `DOCS EDITED: {{ .Destination | safeURL }}|`,
+ "layouts/partials/mypartial1.html", `PARTIAL1_EDITED`,
+ "layouts/partials/mypartial3.html", `PARTIAL3_EDITED`,
+ "layouts/partials/mypartial4.html", `PARTIAL4_EDITED`,
+ "layouts/shortcodes/myshortcode3.html", `SHORT3_EDITED|`,
+ )
+
+ b.Build(BuildCfg{})
+ b.AssertFileContent("public/customview/p1/index.html", `.Render: myrender: Custom View|P4: PARTIAL4_EDITED`)
+ b.AssertFileContent("public/blog/p1/index.html", `<p>EDITED: https://www.google.com|</p>`, "SHORT3_EDITED|")
+ b.AssertFileContent("public/blog/p2/index.html", `PARTIAL1_EDITED`)
+ b.AssertFileContent("public/blog/p3/index.html", `PARTIAL3_EDITED`)
+ // We may add type template support later, keep this for then. b.AssertFileContent("public/docs/docs1/index.html", `DOCS EDITED: https://www.google.com|</p>`)
+ b.AssertFileContent("public/blog/p4/index.html", `IMAGE EDITED: /images/Dragster.jpg|`)
+ b.AssertFileContent("public/blog/p6/index.html", "<p>Inner Link: EDITED: https://www.gohugo.io|</p>")
+
+}
+
+func TestRenderHooksRSS(t *testing.T) {
+
+ b := newTestSitesBuilder(t)
+
+ b.WithTemplates("index.html", `
+{{ $p := site.GetPage "p1.md" }}
+
+P1: {{ $p.Content }}
+
+ `, "index.xml", `
+
+{{ $p2 := site.GetPage "p2.md" }}
+{{ $p3 := site.GetPage "p3.md" }}
+
+P2: {{ $p2.Content }}
+P3: {{ $p3.Content }}
+
+
+ `,
+ "_default/_markup/render-link.html", `html-link: {{ .Destination | safeURL }}|`,
+ "_default/_markup/render-link.rss.xml", `xml-link: {{ .Destination | safeURL }}|`,
+ )
+
+ b.WithContent("p1.md", `---
+title: "p1"