diff --git a/hugolib/config.go b/hugolib/config.go index e6d28051e62..ed040260627 100644 --- a/hugolib/config.go +++ b/hugolib/config.go @@ -66,6 +66,7 @@ func loadDefaultSettings() { viper.SetDefault("dataDir", "data") viper.SetDefault("i18nDir", "i18n") viper.SetDefault("themesDir", "themes") + viper.SetDefault("widgetsDir", "widgets") viper.SetDefault("defaultLayout", "post") viper.SetDefault("buildDrafts", false) viper.SetDefault("buildFuture", false) diff --git a/hugolib/site.go b/hugolib/site.go index b7f4c28c871..3fc7a478943 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -100,6 +100,7 @@ type Site struct { Sections Taxonomy Info SiteInfo Menus Menus + Widgets Widgets timer *nitro.B targets targetList targetListInit sync.Once @@ -177,6 +178,7 @@ type SiteInfo struct { rawAllPages *Pages // Includes absolute all pages, including drafts etc. Files *[]*source.File Menus *Menus + Widgets *Widgets Hugo *HugoInfo Title string RSSLink string @@ -661,6 +663,14 @@ func (s *Site) loadTemplates() { if s.hasTheme() { s.owner.tmpl.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme") } + + // Here we handle the widgets. The site gets all HTML + // code to inject it inside the template, when the + // {{ widgets "mywidgetarea" }} is called. + if err := injectWidgets(s); err != nil { + jww.ERROR.Printf("Failed to load widgets: %s", err) + } + } func (s *Site) prepTemplates(withTemplate func(templ tpl.Template) error) error { @@ -974,6 +984,7 @@ func (s *Site) initializeSiteInfo() { Data: &s.Data, owner: s.owner, pathSpec: helpers.NewPathSpecFromConfig(lang), + Widgets: &s.Widgets, } s.Info.RSSLink = s.Info.permalinkStr(lang.GetString("RSSUri")) @@ -1042,6 +1053,14 @@ func (s *Site) absThemeDir() string { return helpers.AbsPathify(s.themeDir()) } +func (s *Site) widgetDir() string { + return viper.GetString("widgetsDir") +} + +func (s *Site) absWidgetDir() string { + return helpers.AbsPathify(s.widgetDir()) +} + func (s *Site) layoutDir() string { return viper.GetString("layoutDir") } diff --git a/hugolib/widget.go b/hugolib/widget.go new file mode 100644 index 00000000000..8fa164573ba --- /dev/null +++ b/hugolib/widget.go @@ -0,0 +1,146 @@ +// Copyright 2016-present 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 required 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 ( + "github.com/spf13/cast" + "html/template" + + jww "github.com/spf13/jwalterweatherman" + "github.com/spf13/viper" +) + +// TODO See the shortcode system to see the structure + +// Data structures and methods +// =========================== + +// A WidgetEntry represents a widget item defined +// in the site config. +// (TODO: See hugolib/menu.go for the data structure) +type Widget struct { + Type string + Params map[string]interface{} + Identifier string + Weight int + Template *template.Template +} + +func newWidget(widgetType string, options interface{}) (*Widget, error) { + return &Widget{Type: widgetType, Params: options.(map[string]interface{})}, nil +} + +type WidgetArea struct { + Name string + Widgets []*Widget + Template *template.Template +} + +func newWidgetArea(waname string) *WidgetArea { + // TODO ?? + return &WidgetArea{Name: waname, Widgets: nil, Template: nil} +} + +type Widgets map[string]*WidgetArea + +// Internal widgets building +// ========================= + +// WidgetsConfig parses the widgets config variable +// (is a collection) and calls every widget configuration. +func getWidgetsFromConfig() Widgets { + ret := Widgets{} + + if conf := viper.GetStringMap("widgets"); conf != nil { + for waname, widgetarea := range conf { + // wa is a widget area defined in the conf file + wa, err := cast.ToSliceE(widgetarea) + if err != nil { + jww.ERROR.Printf("unable to process widgets in site config\n") + jww.ERROR.Println(err) + } + + // Instantiate a WidgetArea + waobj := newWidgetArea(waname) + + // Retrieve all widgets + for _, w := range wa { + iw, err := cast.ToStringMapE(w) + + if err != nil { + jww.ERROR.Printf("unable to process widget inside widget area in site config\n") + jww.ERROR.Println(err) + } + + // iw represents a widget inside a widget area + wtype := cast.ToString(iw["type"]) + woptions, err := cast.ToStringMapE(iw["options"]) + wobj, err := newWidget(wtype, woptions) + + if err != nil { + jww.ERROR.Printf("unable to instantiate widget: %s\n", iw) + jww.ERROR.Println(err) + } + + // then append it to the widget area object + waobj.Widgets = append(waobj.Widgets, wobj) + } + + // don't forget to append that widget area to the + // Widgets object + ret[waname] = waobj + } + } + + return ret +} + +// instantiateWidget retrieves the widget's files +// and creates the templates +func instantiateWidget(s *Site, wa *WidgetArea, w *Widget) *Widget { + // Load this widget's templates + // using the site object's owner.tmpl + s.owner.tmpl.LoadTemplatesWithPrefix(s.absWidgetDir()+"/"+w.Type+"/layouts", "widgets/"+w.Type) + + return w +} + +// Main widgets entry point +// ======================== + +// This function adds the whole widgets' template code +// in the Site object. This is of type template.HTML. +// This function is called from hugo_sites. +func injectWidgets(s *Site) error { + // Get widgets. This gives all information we need but + // does not already read widget files. + widgets := getWidgetsFromConfig() + + for _, widgetarea := range widgets { + // _ is waname, if ever we need + + for _, w := range widgetarea.Widgets { + w = instantiateWidget(s, widgetarea, w) + } + } + + // We now have all widgets with their templates. + // Generate all widget areas with their templates + // Now the template's content will be used inside + // the main template files inside tpl/template_funcs + // and in the templates using {{ widgets "mywidgetarea" }} + s.Widgets = widgets + + return nil +} diff --git a/tpl/template.go b/tpl/template.go index 2d8ed29435c..5aa4ec6c825 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -23,6 +23,7 @@ import ( "github.com/eknkc/amber" "github.com/spf13/afero" + "github.com/spf13/cast" bp "github.com/spf13/hugo/bufferpool" "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/hugofs" @@ -110,14 +111,23 @@ func partial(name string, contextList ...interface{}) template.HTML { if strings.HasPrefix("partials/", name) { name = name[8:] } + + prefix := "partials" var context interface{} if len(contextList) == 0 { context = nil + } else if pr, err := cast.ToStringE(contextList[0]); err == nil && len(contextList) >= 2 { + // The first parameter of the list (second of the partial + // call) is the prefix + prefix = pr + context = contextList[1] } else { context = contextList[0] } - return ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name) + + prefix += "/" + return ExecuteTemplateToHTML(context, prefix+name, "theme/"+prefix+name) } func executeTemplate(context interface{}, w io.Writer, layouts ...string) { diff --git a/tpl/template_embedded.go b/tpl/template_embedded.go index c418511ac3c..2db606ecc8f 100644 --- a/tpl/template_embedded.go +++ b/tpl/template_embedded.go @@ -60,6 +60,7 @@ func (t *GoHTMLTemplate) EmbedShortcodes() { {{ end }}`) t.AddInternalShortcode("gist.html", ``) t.AddInternalShortcode("tweet.html", `{{ (getJSON "https://api.twitter.com/1/statuses/oembed.json?id=" (index .Params 0)).html | safeHTML }}`) + t.AddInternalShortcode("widgets.html", `{{ widgets (.Get 0) $ }}`) } func (t *GoHTMLTemplate) EmbedTemplates() { @@ -141,6 +142,24 @@ func (t *GoHTMLTemplate) EmbedTemplates() { {{ end }}`) + t.AddInternalTemplate("", "widgets.html", `{{- range .c.Site.Widgets -}} +{{- if eq .Name $._wa -}}{{/* Display only the current widget area */}} +
+ {{- $waname := .Name -}} + {{ range .Widgets -}} +
+ {{ partial (print .Type "/widget.html") "widgets" .Params }} +
+ {{- end }}{{/* end range widgets */}} +
+{{/* end if */}}{{- end -}} +{{/* end range widget areas */}}{{- end -}}`) + + t.AddInternalTemplate("widgets", "text/widget.html", `{{- if isset . "content" -}} + {{- .content | safeHTML -}} +{{- else -}}
Here is a text widget, but there is nothing to print. Please define options.content inside every text widget in your config.
+{{- end -}}`) + t.AddInternalTemplate("", "disqus.html", `{{ if .Site.DisqusShortname }}