Added AuthorList, Author, AuthorSocial, SiteSocial, Image and Video structs
authorDerek Perkins <derek@derekperkins.com>
Tue, 9 Dec 2014 18:33:55 +0000 (11:33 -0700)
committerspf13 <steve.francia@gmail.com>
Fri, 19 Dec 2014 03:26:10 +0000 (22:26 -0500)
Added Page.Author(s) functions
Added schema, opengraph, twitter_cards, google_news metadata templates
Added "" template

hugolib/author.go [new file with mode: 0644]
hugolib/media.go [new file with mode: 0644]
hugolib/page.go
hugolib/site.go
tpl/template_embedded.go

diff --git a/hugolib/author.go b/hugolib/author.go
new file mode 100644 (file)
index 0000000..83803b2
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright © 2013 Steve Francia <spf@spf13.com>.
+//
+// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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
+
+/*
+ *  An author list is a list of all authors and their metadata
+ */
+type AuthorList map[string]Author
+
+/*
+ *  An author contains details about the author of a page
+ */
+type Author struct {
+       GivenName   string
+       FamilyName  string
+       DisplayName string
+       Thumbnail   string
+       Image       string
+       ShortBio    string
+       LongBio     string
+       Email       string
+       Social      AuthorSocial
+}
+
+// AuthorSocial is a place to put social details per author. These are the
+// standard keys that themes will expect to have available, but can be
+// expanded to any others on a per site basis
+// - website
+// - github
+// - facebook
+// - twitter
+// - googleplus
+// - pinterest
+// - instagram
+// - youtube
+// - linkedin
+// - skype
+type AuthorSocial map[string]string
diff --git a/hugolib/media.go b/hugolib/media.go
new file mode 100644 (file)
index 0000000..489c998
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright © 2013 Steve Francia <spf@spf13.com>.
+//
+// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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
+
+// An image contains metadata for images + image sitemaps
+// https://support.google.com/webmasters/answer/178636?hl=en
+type Image struct {
+
+       // The URL of the image. In some cases, the image URL may not be on the
+       // same domain as your main site. This is fine, as long as both domains
+       // are verified in Webmaster Tools. If, for example, you use a
+       // content delivery network (CDN) to host your images, make sure that the
+       // hosting site is verified in Webmaster Tools OR that you submit your
+       // sitemap using robots.txt. In addition, make sure that your robots.txt
+       // file doesn’t disallow the crawling of any content you want indexed.
+       URL     string
+       Title   string
+       Caption string
+       AltText string
+
+       // The geographic location of the image. For example,
+       // <image:geo_location>Limerick, Ireland</image:geo_location>.
+       GeoLocation string
+
+       // A URL to the license of the image.
+       License string
+}
+
+// An video contains metadata for videos + video sitemaps
+// https://support.google.com/webmasters/answer/80471?hl=en
+type Video struct {
+       ThumbnailLoc         string
+       Title                string
+       Description          string
+       ContentLoc           string
+       PlayerLoc            string
+       Duration             string
+       ExpirationDate       string
+       Rating               string
+       ViewCount            string
+       PublicationDate      string
+       FamilyFriendly       string
+       Restriction          string
+       GalleryLoc           string
+       Price                string
+       RequiresSubscription string
+       Uploader             string
+       Live                 string
+}
index 7bf9e7fec14615ae314cd7e1acbc606535e3df06..7eda6958908d16fa45fb6e9dea09857089519dbd 100644 (file)
@@ -40,7 +40,8 @@ type Page struct {
        Summary         template.HTML
        Aliases         []string
        Status          string
-       Images          []string
+       Images          []Image
+       Videos          []Video
        TableOfContents template.HTML
        Truncated       bool
        Draft           bool
@@ -68,7 +69,6 @@ type Source struct {
        Content     []byte
        source.File
 }
-
 type PageMeta struct {
        WordCount      int
        FuzzyWordCount int
@@ -98,6 +98,32 @@ func (p *Page) IsPage() bool {
        return true
 }
 
+func (p *Page) Author() Author {
+       authors := p.Authors()
+
+       for _, author := range authors {
+               return author
+       }
+       return Author{}
+}
+
+func (p *Page) Authors() AuthorList {
+       authorKeys, ok := p.Params["authors"]
+       authors := authorKeys.([]string)
+       if !ok || len(authors) < 1 || len(p.Site.Authors) < 1 {
+               return AuthorList{}
+       }
+
+       al := make(AuthorList)
+       for _, author := range authors {
+               a, ok := p.Site.Authors[author]
+               if ok {
+                       al[author] = a
+               }
+       }
+       return al
+}
+
 func (p *Page) UniqueId() string {
        return p.Source.UniqueId()
 }
index 75f34386cde16cbcbfddb8edce15f21506e08fe1..e2c62a4f52d92f65529a3efed06a9649888fc6c2 100644 (file)
@@ -88,6 +88,8 @@ type targetList struct {
 type SiteInfo struct {
        BaseUrl         template.URL
        Taxonomies      TaxonomyList
+       Authors         AuthorList
+       Social          SiteSocial
        Indexes         *TaxonomyList // legacy, should be identical to Taxonomies
        Sections        Taxonomy
        Pages           *Pages
@@ -105,6 +107,21 @@ type SiteInfo struct {
        BuildDrafts     bool
 }
 
+// SiteSocial is a place to put social details on a site level. These are the
+// standard keys that themes will expect to have available, but can be
+// expanded to any others on a per site basis
+// github
+// facebook
+// facebook_admin
+// twitter
+// twitter_domain
+// googleplus
+// pinterest
+// instagram
+// youtube
+// linkedin
+type SiteSocial map[string]string
+
 func (s *SiteInfo) GetParam(key string) interface{} {
        v := s.Params[strings.ToLower(key)]
 
index 85015c50eab3b7a5f9ed3864da8287395a9d759a..eeab7ba5c73d4a752acb7bee31fc4198729c49b5 100644 (file)
@@ -96,4 +96,83 @@ func (t *GoHtmlTemplate) EmbedTemplates() {
 <noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
 <a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>{{end}}`)
 
+       // Add SEO & Social metadata
+       t.AddInternalTemplate("_default", "opengraph.html", `<meta property="og:title" content="{{ .Title }}" />
+<meta property="og:description" content="{{ if .Description }}{{ .Description }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ end }}{{ end }}" />
+<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
+<meta property="og:url" content="{{ .Permalink }}" />
+{{ with .Params.images }}{{ range first 6 . }}
+  <meta property="og:image" content="{{ . }}" />
+{{ end }}{{ end }}
+
+<meta property="og:updated_time" content="{{ .Date }}"/>{{ with .Params.audio }}
+<meta property="og:audio" content="{{ . }}" />{{ end }}{{ with .Params.locale }}
+<meta property="og:locale" content="{{ . }}" />{{ end }}{{ with .Site.Params.title }}
+<meta property="og:site_name" content="{{ . }}" />{{ end }}{{ with .Params.videos }}
+{{ range .Params.videos }}
+  <meta property="og:video" content="{{ . }}" />
+{{ end }}
+
+<!-- If it is part of a series, link to related articles -->
+{{ $permalink := .Permalink }}
+{{ $siteSeries := .Site.Taxonomies.series }}{{ with .Params.series }}
+{{ range $name := . }}
+  {{ $series := index $siteSeries $name }}
+  {{ range $page := first 6 $series.Pages }}
+    {{ if ne $page.Permalink $permalink }}<meta property="og:see_also" content="{{ $page.Permalink }}" />{{ end }}
+  {{ end }}
+{{ end }}{{ end }}
+
+{{ if .IsPage }}
+{{ range .Site.Authors }}{{ with .Social.facebook }}
+<meta property="article:author" content="https://www.facebook.com/{{ . }}" />{{ end }}{{ with .Site.Social.facebook }}
+<meta property="article:publisher" content="https://www.facebook.com/{{ . }}" />{{ end }}
+<meta property="article:published_time" content="{{ .PublishDate }}" />
+<meta property="article:modified_time" content="{{ .Date }}" />
+<meta property="article:section" content="{{ .Section }}" />
+{{ with .Params.tags }}{{ range first 6 . }}
+  <meta property="article:tag" content="{{ . }}" />{{ end }}{{ end }}
+{{ end }}
+
+<!-- Facebook Page Admin ID for Domain Insights -->
+{{ with .Site.Social.facebook_admin }}<meta property="fb:admins" content="{{ . }}" />{{ end }}`)
+
+       t.AddInternalTemplate("_default", "twitter_cards.html", `{{ if .IsPage }}
+{{ with .Params.images }}
+<!-- Twitter summary card with large image must be at least 280x150px -->
+  <meta name="twitter:card" content="summary_large_image"/>
+  <meta name="twitter:image:src" content="{{ index . 0 }}"/>
+{{ else }}
+  <meta name="twitter:card" content="summary"/>
+{{ end }}
+
+<!-- Twitter Card data -->
+<meta name="twitter:title" content="{{ .Title }}"/>
+<meta name="twitter:description" content="{{ if .Description }}{{ .Description }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ end }}{{ end }}"/>
+{{ with .Site.Social.twitter }}<meta name="twitter:site" content="@{{ . }}"/>{{ end }}
+{{ with .Site.Social.twitter_domain }}<meta name="twitter:domain" content="{{ . }}"/>{{ end }}
+{{ range .Site.Authors }}
+  {{ with .twitter }}<meta name="twitter:creator" content="@{{ . }}"/>{{ end }}
+{{ end }}{{ end }}`)
+
+       t.AddInternalTemplate("_default", "google_news.html", `{{ if .IsPage }}{{ with .Params.news_keywords }}
+  <meta name="news_keywords" content="{{ range $i, $kw := first 10 . }}{{ if $i }},{{ end }}{{ $kw }}{{ end }}" />
+{{ end }}{{ end }}`)
+
+       t.AddInternalTemplate("_default", "schema.html", `{{ with .Site.Social.GooglePlus }}<link rel="publisher" href="{{ . }}"/>{{ end }}
+<meta itemprop="name" content="{{ .Title }}">
+<meta itemprop="description" content="{{ if .Description }}{{ .Description }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ end }}{{ end }}">
+
+{{if .IsPage}}
+<meta itemprop="datePublished" content="{{ .PublishDate }}" />
+<meta itemprop="dateModified" content="{{ .Date }}" />
+<meta itemprop="wordCount" content="{{ .WordCount }}">
+{{ with .Params.images }}{{ range first 6 . }}
+  <meta itemprop="image" content="{{ . }}">
+{{ end }}{{ end }}
+
+<!-- Output all taxonomies as schema.org keywords -->
+<meta itemprop="keywords" content="{{ range $plural, $terms := .Site.Taxonomies }}{{ range $term, $val := $terms }}{{ printf "%s," $term }}{{ end }}{{ end }}" />
+{{ end }}{{ end }}`)
+
 }