Add render template hooks for links and images

This commit also

* revises the change detection for templates used by content files in server mode.
* Adds a Page.RenderString method

Fixes #6545
Fixes #4663
Closes #6043
This commit is contained in:
Bjørn Erik Pedersen
2019-11-27 13:42:36 +01:00
parent 67f3aa72cf
commit e625088ef5
59 changed files with 2234 additions and 542 deletions

View File

@@ -28,6 +28,12 @@ import (
"strings"
"time"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/markup/converter/hooks"
"github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/markup/converter"
@@ -60,7 +66,6 @@ import (
"github.com/gohugoio/hugo/navigation"
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/related"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/page/pagemeta"
"github.com/gohugoio/hugo/source"
"github.com/gohugoio/hugo/tpl"
@@ -801,7 +806,6 @@ func (s *Site) multilingual() *Multilingual {
type whatChanged struct {
source bool
other bool
files map[string]bool
}
@@ -888,10 +892,11 @@ func (s *Site) translateFileEvents(events []fsnotify.Event) []fsnotify.Event {
// It returns whetever the content source was changed.
// TODO(bep) clean up/rewrite this method.
func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) error, events []fsnotify.Event) error {
events = s.filterFileEvents(events)
events = s.translateFileEvents(events)
changeIdentities := make(identity.Identities)
s.Log.DEBUG.Printf("Rebuild for events %q", events)
h := s.h
@@ -902,11 +907,12 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
sourceChanged = []fsnotify.Event{}
sourceReallyChanged = []fsnotify.Event{}
contentFilesChanged []string
tmplChanged = []fsnotify.Event{}
dataChanged = []fsnotify.Event{}
i18nChanged = []fsnotify.Event{}
shortcodesChanged = make(map[string]bool)
sourceFilesChanged = make(map[string]bool)
tmplChanged bool
dataChanged bool
i18nChanged bool
sourceFilesChanged = make(map[string]bool)
// prevent spamming the log on changes
logger = helpers.NewDistinctFeedbackLogger()
@@ -919,33 +925,30 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
cachePartitions = append(cachePartitions, resources.ResourceKeyPartitions(assetsFilename)...)
}
if s.isContentDirEvent(ev) {
logger.Println("Source changed", ev)
sourceChanged = append(sourceChanged, ev)
}
if s.isLayoutDirEvent(ev) {
logger.Println("Template changed", ev)
tmplChanged = append(tmplChanged, ev)
id, found := s.eventToIdentity(ev)
if found {
changeIdentities[id] = id
switch id.Type {
case files.ComponentFolderContent:
logger.Println("Source changed", ev)
sourceChanged = append(sourceChanged, ev)
case files.ComponentFolderLayouts:
logger.Println("Template changed", ev)
tmplChanged = true
case files.ComponentFolderData:
logger.Println("Data changed", ev)
dataChanged = true
case files.ComponentFolderI18n:
logger.Println("i18n changed", ev)
i18nChanged = true
if strings.Contains(ev.Name, "shortcodes") {
shortcode := filepath.Base(ev.Name)
shortcode = strings.TrimSuffix(shortcode, filepath.Ext(shortcode))
shortcodesChanged[shortcode] = true
}
}
if s.isDataDirEvent(ev) {
logger.Println("Data changed", ev)
dataChanged = append(dataChanged, ev)
}
if s.isI18nEvent(ev) {
logger.Println("i18n changed", ev)
i18nChanged = append(dataChanged, ev)
}
}
changed := &whatChanged{
source: len(sourceChanged) > 0 || len(shortcodesChanged) > 0,
other: len(tmplChanged) > 0 || len(i18nChanged) > 0 || len(dataChanged) > 0,
source: len(sourceChanged) > 0,
files: sourceFilesChanged,
}
@@ -960,7 +963,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
s.ResourceSpec.ResourceCache.DeletePartitions(cachePartitions...)
}
if len(tmplChanged) > 0 || len(i18nChanged) > 0 {
if tmplChanged || i18nChanged {
sites := s.h.Sites
first := sites[0]
@@ -989,7 +992,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
}
}
if len(dataChanged) > 0 {
if dataChanged {
s.h.init.data.Reset()
}
@@ -1018,18 +1021,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
sourceFilesChanged[ev.Name] = true
}
for shortcode := range shortcodesChanged {
// There are certain scenarios that, when a shortcode changes,
// it isn't sufficient to just rerender the already parsed shortcode.
// One example is if the user adds a new shortcode to the content file first,
// and then creates the shortcode on the file system.
// To handle these scenarios, we must do a full reprocessing of the
// pages that keeps a reference to the changed shortcode.
pagesWithShortcode := h.findPagesByShortcode(shortcode)
for _, p := range pagesWithShortcode {
contentFilesChanged = append(contentFilesChanged, p.File().Filename())
}
}
h.resetPageStateFromEvents(changeIdentities)
if len(sourceReallyChanged) > 0 || len(contentFilesChanged) > 0 {
var filenamesChanged []string
@@ -1218,20 +1210,14 @@ func (s *Site) initializeSiteInfo() error {
return nil
}
func (s *Site) isI18nEvent(e fsnotify.Event) bool {
return s.BaseFs.SourceFilesystems.IsI18n(e.Name)
}
func (s *Site) eventToIdentity(e fsnotify.Event) (identity.PathIdentity, bool) {
for _, fs := range s.BaseFs.SourceFilesystems.FileSystems() {
if p := fs.Path(e.Name); p != "" {
return identity.NewPathIdentity(fs.Name, p), true
}
}
func (s *Site) isDataDirEvent(e fsnotify.Event) bool {
return s.BaseFs.SourceFilesystems.IsData(e.Name)
}
func (s *Site) isLayoutDirEvent(e fsnotify.Event) bool {
return s.BaseFs.SourceFilesystems.IsLayout(e.Name)
}
func (s *Site) isContentDirEvent(e fsnotify.Event) bool {
return s.BaseFs.IsContent(e.Name)
return identity.PathIdentity{}, false
}
func (s *Site) readAndProcessContent(filenames ...string) error {
@@ -1562,6 +1548,26 @@ var infoOnMissingLayout = map[string]bool{
"404": true,
}
type contentLinkRenderer struct {
templateHandler tpl.TemplateHandler
identity.Provider
templ tpl.Template
}
func (r contentLinkRenderer) Render(w io.Writer, ctx hooks.LinkContext) error {
return r.templateHandler.Execute(r.templ, w, ctx)
}
func (s *Site) lookupTemplate(layouts ...string) (tpl.Template, bool) {
for _, l := range layouts {
if templ, found := s.Tmpl.Lookup(l); found {
return templ, true
}
}
return nil, false
}
func (s *Site) renderForLayouts(name, outputFormat string, d interface{}, w io.Writer, layouts ...string) (err error) {
templ := s.findFirstTemplate(layouts...)
if templ == nil {