Skip to content

Commit

Permalink
Add a widget mechanism to Hugo
Browse files Browse the repository at this point in the history
  • Loading branch information
lebarde committed Nov 13, 2016
1 parent 7cdd230 commit 79591bc
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 54 deletions.
107 changes: 54 additions & 53 deletions hugolib/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,57 +53,58 @@ func LoadGlobalConfig(relativeSourcePath, configFilename string) error {

func loadDefaultSettings() {
viper.SetDefault("cleanDestinationDir", false)
viper.SetDefault("Watch", false)
viper.SetDefault("MetaDataFormat", "toml")
viper.SetDefault("Disable404", false)
viper.SetDefault("DisableRSS", false)
viper.SetDefault("DisableSitemap", false)
viper.SetDefault("DisableRobotsTXT", false)
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("StaticDir", "static")
viper.SetDefault("ArchetypeDir", "archetypes")
viper.SetDefault("PublishDir", "public")
viper.SetDefault("DataDir", "data")
viper.SetDefault("I18nDir", "i18n")
viper.SetDefault("ThemesDir", "themes")
viper.SetDefault("DefaultLayout", "post")
viper.SetDefault("BuildDrafts", false)
viper.SetDefault("BuildFuture", false)
viper.SetDefault("BuildExpired", false)
viper.SetDefault("UglyURLs", false)
viper.SetDefault("Verbose", false)
viper.SetDefault("IgnoreCache", false)
viper.SetDefault("CanonifyURLs", false)
viper.SetDefault("RelativeURLs", false)
viper.SetDefault("RemovePathAccents", false)
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
viper.SetDefault("Permalinks", make(PermalinkOverrides, 0))
viper.SetDefault("Sitemap", Sitemap{Priority: -1, Filename: "sitemap.xml"})
viper.SetDefault("DefaultExtension", "html")
viper.SetDefault("PygmentsStyle", "monokai")
viper.SetDefault("PygmentsUseClasses", false)
viper.SetDefault("PygmentsCodeFences", false)
viper.SetDefault("PygmentsOptions", "")
viper.SetDefault("DisableLiveReload", false)
viper.SetDefault("PluralizeListTitles", true)
viper.SetDefault("PreserveTaxonomyNames", false)
viper.SetDefault("ForceSyncStatic", false)
viper.SetDefault("FootnoteAnchorPrefix", "")
viper.SetDefault("FootnoteReturnLinkContents", "")
viper.SetDefault("NewContentEditor", "")
viper.SetDefault("Paginate", 10)
viper.SetDefault("PaginatePath", "page")
viper.SetDefault("Blackfriday", helpers.NewBlackfriday(viper.GetViper()))
viper.SetDefault("RSSUri", "index.xml")
viper.SetDefault("SectionPagesMenu", "")
viper.SetDefault("DisablePathToLower", false)
viper.SetDefault("HasCJKLanguage", false)
viper.SetDefault("EnableEmoji", false)
viper.SetDefault("PygmentsCodeFencesGuessSyntax", false)
viper.SetDefault("UseModTimeAsFallback", false)
viper.SetDefault("CurrentContentLanguage", helpers.NewDefaultLanguage())
viper.SetDefault("DefaultContentLanguage", "en")
viper.SetDefault("DefaultContentLanguageInSubdir", false)
viper.SetDefault("EnableMissingTranslationPlaceholders", false)
viper.SetDefault("watch", false)
viper.SetDefault("metaDataFormat", "toml")
viper.SetDefault("disable404", false)
viper.SetDefault("disableRSS", false)
viper.SetDefault("disableSitemap", false)
viper.SetDefault("disableRobotsTXT", false)
viper.SetDefault("contentDir", "content")
viper.SetDefault("layoutDir", "layouts")
viper.SetDefault("staticDir", "static")
viper.SetDefault("archetypeDir", "archetypes")
viper.SetDefault("publishDir", "public")
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)
viper.SetDefault("buildExpired", false)
viper.SetDefault("uglyURLs", false)
viper.SetDefault("verbose", false)
viper.SetDefault("ignoreCache", false)
viper.SetDefault("canonifyURLs", false)
viper.SetDefault("relativeURLs", false)
viper.SetDefault("removePathAccents", false)
viper.SetDefault("taxonomies", map[string]string{"tag": "tags", "category": "categories"})
viper.SetDefault("permalinks", make(PermalinkOverrides, 0))
viper.SetDefault("sitemap", Sitemap{Priority: -1, Filename: "sitemap.xml"})
viper.SetDefault("defaultExtension", "html")
viper.SetDefault("pygmentsStyle", "monokai")
viper.SetDefault("pygmentsUseClasses", false)
viper.SetDefault("pygmentsCodeFences", false)
viper.SetDefault("pygmentsOptions", "")
viper.SetDefault("disableLiveReload", false)
viper.SetDefault("pluralizeListTitles", true)
viper.SetDefault("preserveTaxonomyNames", false)
viper.SetDefault("forceSyncStatic", false)
viper.SetDefault("footnoteAnchorPrefix", "")
viper.SetDefault("footnoteReturnLinkContents", "")
viper.SetDefault("newContentEditor", "")
viper.SetDefault("paginate", 10)
viper.SetDefault("paginatePath", "page")
viper.SetDefault("blackfriday", helpers.NewBlackfriday(viper.GetViper()))
viper.SetDefault("rSSUri", "index.xml")
viper.SetDefault("sectionPagesMenu", "")
viper.SetDefault("disablePathToLower", false)
viper.SetDefault("hasCJKLanguage", false)
viper.SetDefault("enableEmoji", false)
viper.SetDefault("pygmentsCodeFencesGuessSyntax", false)
viper.SetDefault("useModTimeAsFallback", false)
viper.SetDefault("currentContentLanguage", helpers.NewDefaultLanguage())
viper.SetDefault("defaultContentLanguage", "en")
viper.SetDefault("defaultContentLanguageInSubdir", false)
viper.SetDefault("enableMissingTranslationPlaceholders", false)
}
19 changes: 19 additions & 0 deletions hugolib/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ type Site struct {
Sections Taxonomy
Info SiteInfo
Menus Menus
Widgets Widgets
timer *nitro.B
targets targetList
targetListInit sync.Once
Expand Down Expand Up @@ -173,6 +174,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
Expand Down Expand Up @@ -633,6 +635,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 {
Expand Down Expand Up @@ -944,6 +954,7 @@ func (s *Site) initializeSiteInfo() {
Permalinks: permalinks,
Data: &s.Data,
owner: s.owner,
Widgets: &s.Widgets,
}
}

Expand Down Expand Up @@ -1018,6 +1029,14 @@ func (s *Site) getThemeDir(path string) string {
return getRealDir(s.absThemeDir(), path)
}

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")
}
Expand Down
146 changes: 146 additions & 0 deletions hugolib/widget.go
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 11 additions & 1 deletion tpl/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -106,14 +107,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) {
Expand Down
19 changes: 19 additions & 0 deletions tpl/template_embedded.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func (t *GoHTMLTemplate) EmbedShortcodes() {
{{ end }}`)
t.AddInternalShortcode("gist.html", `<script src="//gist.github.com/{{ index .Params 0 }}/{{ index .Params 1 }}.js{{if len .Params | eq 3 }}?file={{ index .Params 2 }}{{end}}"></script>`)
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() {
Expand Down Expand Up @@ -141,6 +142,24 @@ func (t *GoHTMLTemplate) EmbedTemplates() {
</ul>
{{ end }}`)

t.AddInternalTemplate("", "widgets.html", `{{- range .c.Site.Widgets -}}
{{- if eq .Name $._wa -}}{{/* Display only the current widget area */}}
<div class="widget-area widget-area-{{ .Name }}">
{{- $waname := .Name -}}
{{ range .Widgets -}}
<div class="widget widget-{{ .Type }}">
{{ partial (print .Type "/widget.html") "widgets" .Params }}
</div>
{{- end }}{{/* end range widgets */}}
</div>
{{/* end if */}}{{- end -}}
{{/* end range widget areas */}}{{- end -}}`)

t.AddInternalTemplate("widgets", "text/widget.html", `{{- if isset . "content" -}}
{{- .content | safeHTML -}}
{{- else -}}<pre>Here is a text widget, but there is nothing to print. Please define options.content inside every text widget in your config.</pre>
{{- end -}}`)

t.AddInternalTemplate("", "disqus.html", `{{ if .Site.DisqusShortname }}<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_shortname = '{{ .Site.DisqusShortname }}';
Expand Down
Loading

0 comments on commit 79591bc

Please sign in to comment.