jekyll-seo-tag 2.2.3 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8b844bd7fdb4699fbbb7cd83e5c1e85f21f57c99
4
- data.tar.gz: 6331b2bca4a5a48b8d5566ea7fb747f7a9765016
3
+ metadata.gz: aa7625108b352ef1b563698519ba698b591efdde
4
+ data.tar.gz: 1fd0e7b6a24d046e81ad0265738aa9687cdca1db
5
5
  SHA512:
6
- metadata.gz: c6e5d187d67553021fbf2270323fad748d10da302816ed68d6099bc17db2a0d4712643ca9e38b5c02ed7a268fb03d2296fa47d32cb70c07fb402b6dfa7fc6d8b
7
- data.tar.gz: 134e65c286346234426d1298645d4d4cdbd331a41aafd631bf7aa8707141511416f384502fe3cf8cd5881ec5814c817fb8291bde8cfc9146e3b496fe71edde39
6
+ metadata.gz: a0f5cc15eba20e905955a1095ece08f87b74150b928be795934e5a6a67aea09463ffdf2b66176ac39d7948cfb07f65676ddede3ccb86bcc6bfc2e1c1a758c28c
7
+ data.tar.gz: 79bd26c6530690c2b886d5ba66c0b97607bb979d0cd181061222ad1c120fddf9b53b2fa104a27740042e7bd49851620654db34c4db77e207d1e18fe2310b1134
data/.gitignore CHANGED
@@ -9,3 +9,4 @@
9
9
  /tmp/
10
10
  /bin/
11
11
  *.gem
12
+ _site
data/Gemfile CHANGED
@@ -1,5 +1,5 @@
1
1
  source "https://rubygems.org"
2
- require "json"
3
- require "open-uri"
4
2
 
5
3
  gemspec
4
+
5
+ gem "github-pages", :group => :jekyll_plugins
@@ -1,5 +1,22 @@
1
1
  ## HEAD
2
2
 
3
+ ## 2.3.0
4
+
5
+ ### Minor Enhancements
6
+
7
+ * Use canonical_url specified in page if present #211
8
+ * Fix for image.path causing an invalid url error #228
9
+ * Ensure `site.data.authors` is properly formatted before attempting to retrieve author meta #227
10
+ * Convert author, image, and JSON-LD to dedicated drops #229
11
+ * Cache parsed template #231
12
+ * Define path with `__dir__` #232
13
+
14
+ ### Documentation
15
+
16
+ * gems: is deprecated in current Jekyll version of github-pages #230
17
+
18
+ ## 2.2.3
19
+
3
20
  * Guard against the author's Twitter handle being Nil when stripping @'s #203
4
21
  * Guard against empty title or description strings #206
5
22
 
@@ -0,0 +1,33 @@
1
+ ## About Jekyll SEO Tag
2
+
3
+ A Jekyll plugin to add metadata tags for search engines and social networks to better index and display your site's content.
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/jekyll-seo-tag.svg)](https://badge.fury.io/rb/jekyll-seo-tag) [![Build Status](https://travis-ci.org/jekyll/jekyll-seo-tag.svg)](https://travis-ci.org/jekyll/jekyll-seo-tag)
6
+
7
+ ## What it does
8
+
9
+ Jekyll SEO Tag adds the following meta tags to your site:
10
+
11
+ * Page title, with site title or description appended
12
+ * Page description
13
+ * Canonical URL
14
+ * Next and previous URLs on paginated pages
15
+ * [JSON-LD Site and post metadata](https://developers.google.com/structured-data/) for richer indexing
16
+ * [Open Graph](http://ogp.me/) title, description, site title, and URL (for Facebook, LinkedIn, etc.)
17
+ * [Twitter Summary Card](https://dev.twitter.com/cards/overview) metadata
18
+
19
+ While you could theoretically add the necessary metadata tags yourself, Jekyll SEO Tag provides a battle-tested template of crowdsourced best-practices.
20
+
21
+ ## What it doesn't do
22
+
23
+ Jekyll SEO tag is designed to output machine-readable metadata for search engines and social networks to index and display. If you're looking for something to analyze your Jekyll site's structure and content (e.g., more traditional SEO optimization), take a look at [The Jekyll SEO Gem](https://github.com/pmarsceill/jekyll-seo-gem).
24
+
25
+ Jekyll SEO tag isn't designed to accommodate every possible use case. It should work for most site out of the box and without a laundry list of configuration options that serve only to confuse most users.
26
+
27
+ ## Documentation
28
+
29
+ For more information, see:
30
+
31
+ * [Installation](installation.md)
32
+ * [Usage](usage.md)
33
+ * [Advanced usage](advanced-usage.md)
@@ -0,0 +1,10 @@
1
+ title: Jekyll SEO Tag
2
+ description: A Jekyll plugin to add metadata tags for search engines and social networks to better index and display your site's content.
3
+
4
+ permalink: pretty
5
+
6
+ gems:
7
+ - jekyll-seo-tag
8
+ - jekyll-sitemap
9
+
10
+ theme: jekyll-theme-primer
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <link href="{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}" rel="stylesheet">
7
+ {% seo %}
8
+ </head>
9
+ <body>
10
+ <div class="container markdown-body">
11
+ <h1>{{ site.title }}</h1>
12
+
13
+ {{ content }}
14
+ </div>
15
+ <script src="{{ "assets/javascript/anchor-js/anchor.min.js" | relative_url }}"></script>
16
+ <script>anchors.add();</script>
17
+ </body>
18
+ </html>
@@ -0,0 +1,138 @@
1
+ ## Advanced usage
2
+
3
+ Jekyll SEO Tag is designed to implement SEO best practices by default and to be the right fit for most sites right out of the box. If for some reason, you need more control over the output, read on:
4
+
5
+ ### Disabling `<title>` output
6
+
7
+ If for some reason, you don't want the plugin to output `<title>` tags on each page, simply invoke the plugin within your template like so:
8
+
9
+ ```
10
+ {% seo title=false %}
11
+ ```
12
+
13
+ ### Author information
14
+
15
+ Author information is used to propagate the `creator` field of Twitter summary cards. This should be an author-specific, not site-wide Twitter handle (the site-wide username be stored as `site.twitter.username`).
16
+
17
+ *TL;DR: In most cases, put `author: [your Twitter handle]` in the document's front matter, for sites with multiple authors. If you need something more complicated, read on.*
18
+
19
+ There are several ways to convey this author-specific information. Author information is found in the following order of priority:
20
+
21
+ 1. An `author` object, in the documents's front matter, e.g.:
22
+
23
+ ```yml
24
+ author:
25
+ twitter: benbalter
26
+ ```
27
+
28
+ 2. An `author` object, in the site's `_config.yml`, e.g.:
29
+
30
+ ```yml
31
+ author:
32
+ twitter: benbalter
33
+ ```
34
+
35
+ 3. `site.data.authors[author]`, if an author is specified in the document's front matter, and a corresponding key exists in `site.data.authors`. E.g., you have the following in the document's front matter:
36
+
37
+ ```yml
38
+ author: benbalter
39
+ ```
40
+
41
+ And you have the following in `_data/authors.yml`:
42
+
43
+ ```yml
44
+ benbalter:
45
+ picture: /img/benbalter.png
46
+ twitter: jekyllrb
47
+
48
+ potus:
49
+ picture: /img/potus.png
50
+ twitter: whitehouse
51
+ ```
52
+
53
+ In the above example, the author `benbalter`'s Twitter handle will be resolved to `@jekyllrb`. This allows you to centralize author information in a single `_data/authors` file for site with many authors that require more than just the author's username.
54
+
55
+ *Pro-tip: If `authors` is present in the document's front matter as an array (and `author` is not), the plugin will use the first author listed, as Twitter supports only one author.*
56
+
57
+ 4. An author in the document's front matter (the simplest way), e.g.:
58
+
59
+ ```yml
60
+ author: benbalter
61
+ ```
62
+
63
+ 5. An author in the site's `_config.yml`, e.g.:
64
+
65
+ ```yml
66
+ author: benbalter
67
+ ```
68
+
69
+ ### Customizing JSON-LD output
70
+
71
+ The following options can be set for any particular page. While the default options are meant to serve most users in the most common circumstances, there may be situations where more precise control is necessary.
72
+
73
+ * `seo`
74
+ * `name` - If the name of the thing that the page represents is different from the page title. (i.e.: "Frank's Café" vs "Welcome to Frank's Café")
75
+ * `type` - The type of things that the page represents. This must be a [Schema.org type](http://schema.org/docs/schemas.html), and will probably usually be something like [`BlogPosting`](http://schema.org/BlogPosting), [`NewsArticle`](http://schema.org/NewsArticle), [`Person`](http://schema.org/Person), [`Organization`](http://schema.org/Organization), etc.
76
+ * `links` - An array of other URLs that represent the same thing that this page represents. For instance, Jane's bio page might include links to Jane's GitHub and Twitter profiles.
77
+
78
+ ### Customizing image output
79
+
80
+ For most users, setting `image: [path-to-image]` on a per-page basis should be enough. If you need more control over how images are represented, the `image` property can also be an object, with the following options:
81
+
82
+ * `path` - The relative path to the image. Same as `image: [path-to-image]`
83
+ * `height` - The height of the Open Graph (`og:image`) image
84
+ * `width` - The width of the Open Graph (`og:image`) image
85
+
86
+ You can use any of the above, optional properties, like so:
87
+
88
+ ```yml
89
+ image:
90
+ path: /img/twitter.png
91
+ height: 100
92
+ width: 100
93
+ ```
94
+
95
+ ### Setting a default image
96
+
97
+ You can define a default image using [Front Matter default](https://jekyllrb.com/docs/configuration/#front-matter-defaults), to provide a default Twitter Card or OGP image to all of your posts and pages.
98
+
99
+ Here is a very basic example, that you are encouraged to adapt to your needs:
100
+
101
+ ```yml
102
+ defaults:
103
+ - scope:
104
+ path: ""
105
+ values:
106
+ image: /assets/images/default-card.png
107
+ ```
108
+
109
+ ### SmartyPants Titles
110
+
111
+ Titles will be processed using [Jekyll's `smartify` filter](https://jekyllrb.com/docs/templates/). This will use SmartyPants to translate plain ASCII punctuation into "smart" typographic punctuation. This will not render or strip any Markdown you may be using in a page title.
112
+
113
+ ### Setting customized Canonical URL
114
+
115
+ You can set custom Canonical URL for a page by specifying canonical_url option in page front-matter.
116
+ E.g., you have the following in the page's front matter:
117
+ ```yml
118
+ layout: post
119
+ title: Title of Your Post
120
+ canonical_url: 'https://github.com/jekyll/jekyll-seo-tag/'
121
+ ```
122
+
123
+ Which will generate canonical_url with specified link in canonical_url.
124
+ ```html
125
+ <link rel="canonical" href="https://github.com/jekyll/jekyll-seo-tag/" />
126
+ ```
127
+
128
+ If no canonical_url option was specified, then uses page url for generating canonical_url.
129
+ E.g., you have not specified canonical_url in front-matter:
130
+ ```yml
131
+ layout: post
132
+ title: Title of Your Post
133
+ ```
134
+
135
+ Which will generate following canonical_url:
136
+ ```html
137
+ <link rel="canonical" href="http://yoursite.com/title-of-your-post" />
138
+ ```
@@ -0,0 +1,20 @@
1
+ # Installing Jekyll SEO Tag
2
+
3
+ 1. Add the following to your site's `Gemfile`:
4
+
5
+ ```ruby
6
+ gem 'jekyll-seo-tag'
7
+ ```
8
+
9
+ 2. Add the following to your site's `_config.yml`:
10
+
11
+ ```yml
12
+ plugins:
13
+ - jekyll-seo-tag
14
+ ```
15
+
16
+ 3. Add the following right before `</head>` in your site's template(s):
17
+
18
+ ```liquid
19
+ {% seo %}
20
+ ```
@@ -0,0 +1,67 @@
1
+ ## Usage
2
+
3
+ The SEO tag will respect any of the following if included in your site's `_config.yml` (and simply not include them if they're not defined):
4
+
5
+ * `title` - Your site's title (e.g., Ben's awesome site, The GitHub Blog, etc.)
6
+ * `description` - A short description (e.g., A blog dedicated to reviewing cat gifs)
7
+ * `url` - The full URL to your site. Note: `site.github.url` will be used by default.
8
+ * `author` - global author information (see below)
9
+ * `twitter:username` - The site's Twitter handle. You'll want to describe it like so:
10
+
11
+ ```yml
12
+ twitter:
13
+ username: benbalter
14
+ ```
15
+
16
+ * `facebook` - The following properties are available:
17
+ * `facebook:app_id` - a Facebook app ID for Facebook insights
18
+ * `facebook:publisher` - a Facebook page URL or ID of the publishing entity
19
+ * `facebook:admins` - a Facebook user ID for domain insights linked to a personal account
20
+
21
+ You'll want to describe one or more like so:
22
+
23
+ ```yml
24
+ facebook:
25
+ app_id: 1234
26
+ publisher: 1234
27
+ admins: 1234
28
+ ```
29
+
30
+ * `logo` - URL to a site-wide logo (e.g., `/assets/your-company-logo.png`)
31
+ * `social` - For [specifying social profiles](https://developers.google.com/structured-data/customize/social-profiles). The following properties are available:
32
+ * `name` - If the user or organization name differs from the site's name
33
+ * `links` - An array of links to social media profiles.
34
+ * `date_modified` - Manually specify the `dateModified` field in the JSON-LD output to override Jekyll's own `dateModified`. This field will take **first priority** for the `dateModified` JSON-LD output. This is useful when the file timestamp does not match the true time that the content was modified. A user may also install [Last Modified At](https://github.com/gjtorikian/jekyll-last-modified-at) which will offer an alternative way of providing for the `dateModified` field.
35
+
36
+ ```yml
37
+ social:
38
+ name: Ben Balter
39
+ links:
40
+ - https://twitter.com/BenBalter
41
+ - https://www.facebook.com/ben.balter
42
+ - https://www.linkedin.com/in/BenBalter
43
+ - https://plus.google.com/+BenBalter
44
+ - https://github.com/benbalter
45
+ - https://keybase.io/benbalter
46
+ ```
47
+
48
+ * `google_site_verification` for verifying ownership via Google webmaster tools
49
+ * Alternatively, verify ownership with several services at once using the following format:
50
+
51
+ ```yml
52
+ webmaster_verifications:
53
+ google: 1234
54
+ bing: 1234
55
+ alexa: 1234
56
+ yandex: 1234
57
+ ```
58
+
59
+ * `lang` - The locale these tags are marked up in. Of the format `language_TERRITORY`. Default is `en_US`.
60
+
61
+ The SEO tag will respect the following YAML front matter if included in a post, page, or document:
62
+
63
+ * `title` - The title of the post, page, or document
64
+ * `description` - A short description of the page's content
65
+ * `image` - URL to an image associated with the post, page, or document (e.g., `/assets/page-pic.jpg`)
66
+ * `author` - Page-, post-, or document-specific author information (see below)
67
+ * `lang` - Page-, post-, or document-specific language information
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- lib = File.expand_path("../lib", __FILE__)
3
+ lib = File.expand_path("lib", __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require "jekyll-seo-tag/version"
6
6
 
@@ -3,9 +3,13 @@ require "jekyll-seo-tag/version"
3
3
 
4
4
  module Jekyll
5
5
  class SeoTag < Liquid::Tag
6
- autoload :JSONLD, "jekyll-seo-tag/json_ld"
7
- autoload :Drop, "jekyll-seo-tag/drop"
8
- autoload :Filters, "jekyll-seo-tag/filters"
6
+ autoload :JSONLD, "jekyll-seo-tag/json_ld"
7
+ autoload :AuthorDrop, "jekyll-seo-tag/author_drop"
8
+ autoload :ImageDrop, "jekyll-seo-tag/image_drop"
9
+ autoload :JSONLDDrop, "jekyll-seo-tag/json_ld_drop"
10
+ autoload :UrlHelper, "jekyll-seo-tag/url_helper"
11
+ autoload :Drop, "jekyll-seo-tag/drop"
12
+ autoload :Filters, "jekyll-seo-tag/filters"
9
13
 
10
14
  attr_accessor :context
11
15
 
@@ -27,7 +31,7 @@ module Jekyll
27
31
 
28
32
  def render(context)
29
33
  @context = context
30
- template.render!(payload, info)
34
+ SeoTag.template.render!(payload, info)
31
35
  end
32
36
 
33
37
  private
@@ -59,19 +63,23 @@ module Jekyll
59
63
  }
60
64
  end
61
65
 
62
- def template
63
- @template ||= Liquid::Template.parse template_contents
64
- end
66
+ class << self
67
+ def template
68
+ @template ||= Liquid::Template.parse template_contents
69
+ end
65
70
 
66
- def template_contents
67
- @template_contents ||= begin
68
- File.read(template_path).gsub(MINIFY_REGEX, "")
71
+ private
72
+
73
+ def template_contents
74
+ @template_contents ||= begin
75
+ File.read(template_path).gsub(MINIFY_REGEX, "")
76
+ end
69
77
  end
70
- end
71
78
 
72
- def template_path
73
- @template_path ||= begin
74
- File.expand_path "./template.html", File.dirname(__FILE__)
79
+ def template_path
80
+ @template_path ||= begin
81
+ File.expand_path "./template.html", File.dirname(__FILE__)
82
+ end
75
83
  end
76
84
  end
77
85
  end
@@ -0,0 +1,85 @@
1
+ module Jekyll
2
+ class SeoTag
3
+ # A drop representing the current page's author
4
+ #
5
+ # Author name will be pulled from:
6
+ #
7
+ # 1. The page's `author` key
8
+ # 2. The first author in the page's `authors` key
9
+ # 3. The `author` key in the site config
10
+ #
11
+ # If the result from the name search is a string, we'll also check
12
+ # for additional author metadata in `site.data.authors`
13
+ class AuthorDrop < Jekyll::Drops::Drop
14
+ # Initialize a new AuthorDrop
15
+ #
16
+ # page - The page hash (e.g., Page#to_liquid)
17
+ # site - The Jekyll::Drops::SiteDrop
18
+ def initialize(page: nil, site: nil)
19
+ raise ArgumentError unless page && site
20
+ @mutations = {}
21
+ @page = page
22
+ @site = site
23
+ end
24
+
25
+ # AuthorDrop#to_s should return name, allowing the author drop to safely
26
+ # replace `page.author`, if necessary, and remain backwards compatible
27
+ def name
28
+ author_hash["name"]
29
+ end
30
+ alias_method :to_s, :name
31
+
32
+ def twitter
33
+ return @twitter if defined? @twitter
34
+ twitter = author_hash["twitter"] || author_hash["name"]
35
+ @twitter = twitter.is_a?(String) ? twitter.sub(%r!^@!, "") : nil
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :page
41
+ attr_reader :site
42
+
43
+ # Finds the page author in the page.author, page.authors, or site.author
44
+ #
45
+ # Returns a string or hash representing the author
46
+ def resolved_author
47
+ return @resolved_author if defined? @resolved_author
48
+ sources = [page["author"]]
49
+ sources << page["authors"].first if page["authors"].is_a?(Array)
50
+ sources << site["author"]
51
+ @resolved_author = sources.find { |s| !s.to_s.empty? }
52
+ end
53
+
54
+ # If resolved_author is a string, attempts to find coresponding author
55
+ # metadata in `site.data.authors`
56
+ #
57
+ # Returns a hash representing additional metadata or an empty hash
58
+ def site_data_hash
59
+ @site_data_hash ||= begin
60
+ return {} unless resolved_author.is_a?(String)
61
+ return {} unless site.data["authors"].is_a?(Hash)
62
+ author_hash = site.data["authors"][resolved_author]
63
+ author_hash.is_a?(Hash) ? author_hash : {}
64
+ end
65
+ end
66
+
67
+ # Returns the normalized author hash representing the page author,
68
+ # including site-wide metadata if the author is provided as a string,
69
+ # or an empty hash, if the author cannot be resolved
70
+ def author_hash
71
+ if resolved_author.is_a? Hash
72
+ resolved_author
73
+ elsif resolved_author.is_a? String
74
+ { "name" => resolved_author }.merge(site_data_hash)
75
+ else
76
+ {}
77
+ end
78
+ end
79
+
80
+ # Since author_hash is aliased to fallback_data, any values in the hash
81
+ # will be exposed via the drop, allowing support for arbitrary metadata
82
+ alias_method :fallback_data, :author_hash
83
+ end
84
+ end
85
+ end
@@ -1,7 +1,7 @@
1
1
  module Jekyll
2
2
  class SeoTag
3
3
  class Drop < Jekyll::Drops::Drop
4
- include Jekyll::SeoTag::JSONLD
4
+ include Jekyll::SeoTag::UrlHelper
5
5
 
6
6
  TITLE_SEPARATOR = " | ".freeze
7
7
  FORMAT_STRING_METHODS = %i[
@@ -72,29 +72,21 @@ module Jekyll
72
72
  end
73
73
  end
74
74
 
75
- # Returns a nil or a hash representing the author
76
- # Author name will be pulled from:
77
- #
78
- # 1. The `author` key, if the key is a string
79
- # 2. The first author in the `authors` key
80
- # 3. The `author` key in the site config
81
- #
82
- # If the result from the name search is a string, we'll also check
83
- # to see if the author exists in `site.data.authors`
75
+ # A drop representing the page author
84
76
  def author
85
- @author ||= begin
86
- return if author_string_or_hash.to_s.empty?
87
-
88
- author = if author_string_or_hash.is_a?(String)
89
- author_hash(author_string_or_hash)
90
- else
91
- author_string_or_hash
92
- end
93
-
94
- author["twitter"] ||= author["name"]
95
- author["twitter"].delete! "@" if author["twitter"]
96
- author.to_liquid
97
- end
77
+ @author ||= AuthorDrop.new(:page => page, :site => site)
78
+ end
79
+
80
+ # A drop representing the JSON-LD output
81
+ def json_ld
82
+ @json_ld ||= JSONLDDrop.new(self)
83
+ end
84
+
85
+ # Returns a Drop representing the page's image
86
+ # Returns nil if the image has no path, to preserve backwards compatability
87
+ def image
88
+ @image ||= ImageDrop.new(:page => page, :context => @context)
89
+ @image if @image.path
98
90
  end
99
91
 
100
92
  def date_modified
@@ -149,41 +141,17 @@ module Jekyll
149
141
  end
150
142
  end
151
143
 
152
- # Returns nil or a hash representing the page image
153
- # The image hash will always contain a path, pulled from:
154
- #
155
- # 1. The `image` key if it's a string
156
- # 2. The `image.path` key if it's a hash
157
- # 3. The `image.facebook` key
158
- # 4. The `image.twitter` key
159
- #
160
- # The resulting path is always an absolute URL
161
- def image
162
- return @image if defined?(@image)
163
-
164
- image = page["image"]
165
- return @image = nil unless image
166
-
167
- image = { "path" => image } if image.is_a?(String)
168
- image["path"] ||= image["facebook"] || image["twitter"]
169
- return @image = nil unless image["path"]
170
-
171
- unless absolute_url? image["path"]
172
- image["path"] = filters.absolute_url image["path"]
173
- end
174
-
175
- image["path"] = filters.uri_escape image["path"]
176
-
177
- @image = image.to_liquid
178
- end
179
-
180
144
  def page_lang
181
145
  @page_lang ||= page["lang"] || site["lang"] || "en_US"
182
146
  end
183
147
 
184
148
  def canonical_url
185
149
  @canonical_url ||= begin
186
- filters.absolute_url(page["url"]).to_s.gsub(%r!/index\.html$!, "/")
150
+ if page["canonical_url"].to_s.empty?
151
+ filters.absolute_url(page["url"]).to_s.gsub(%r!/index\.html$!, "/")
152
+ else
153
+ page["canonical_url"]
154
+ end
187
155
  end
188
156
  end
189
157
 
@@ -211,13 +179,6 @@ module Jekyll
211
179
  @fallback_data ||= {}
212
180
  end
213
181
 
214
- def absolute_url?(string)
215
- return unless string
216
- Addressable::URI.parse(string).absolute?
217
- rescue Addressable::URI::InvalidURIError
218
- nil
219
- end
220
-
221
182
  def format_string(string)
222
183
  string = FORMAT_STRING_METHODS.reduce(string) do |memo, method|
223
184
  filters.public_send(method, memo)
@@ -226,26 +187,6 @@ module Jekyll
226
187
  string unless string.empty?
227
188
  end
228
189
 
229
- def author_string_or_hash
230
- @author_string_or_hash ||= begin
231
- author = page["author"]
232
- author = page["authors"][0] if author.to_s.empty? && page["authors"]
233
- author = site["author"] if author.to_s.empty?
234
- author
235
- end
236
- end
237
-
238
- def author_hash(author_string)
239
- if site.data["authors"] && site.data["authors"][author_string]
240
- hash = site.data["authors"][author_string]
241
- hash["name"] ||= author_string
242
- hash["twitter"] ||= author_string
243
- hash
244
- else
245
- { "name" => author_string }
246
- end
247
- end
248
-
249
190
  def seo_name
250
191
  @seo_name ||= format_string(page_seo["name"]) if page_seo["name"]
251
192
  end
@@ -0,0 +1,70 @@
1
+ module Jekyll
2
+ class SeoTag
3
+ # A drop representing the page image
4
+ # The image path will be pulled from:
5
+ #
6
+ # 1. The `image` key if it's a string
7
+ # 2. The `image.path` key if it's a hash
8
+ # 3. The `image.facebook` key
9
+ # 4. The `image.twitter` key
10
+ class ImageDrop < Jekyll::Drops::Drop
11
+ include Jekyll::SeoTag::UrlHelper
12
+
13
+ # Initialize a new ImageDrop
14
+ #
15
+ # page - The page hash (e.g., Page#to_liquid)
16
+ # context - the Liquid::Context
17
+ def initialize(page: nil, context: nil)
18
+ raise ArgumentError unless page && context
19
+ @mutations = {}
20
+ @page = page
21
+ @context = context
22
+ end
23
+
24
+ # Called path for backwards compatability, this is really
25
+ # the escaped, absolute URL representing the page's image
26
+ # Returns nil if no image path can be determined
27
+ def path
28
+ @path ||= filters.uri_escape(absolute_url) if absolute_url
29
+ end
30
+ alias_method :to_s, :path
31
+
32
+ private
33
+
34
+ attr_accessor :page
35
+ attr_accessor :context
36
+
37
+ # The normalized image hash with a `path` key (which may be nil)
38
+ def image_hash
39
+ @image_hash ||= if page["image"].is_a?(Hash)
40
+ { "path" => nil }.merge(page["image"])
41
+ elsif page["image"].is_a?(String)
42
+ { "path" => page["image"] }
43
+ else
44
+ { "path" => nil }
45
+ end
46
+ end
47
+ alias_method :fallback_data, :image_hash
48
+
49
+ def raw_path
50
+ @raw_path ||= begin
51
+ image_hash["path"] || image_hash["facebook"] || image_hash["twitter"]
52
+ end
53
+ end
54
+
55
+ def absolute_url
56
+ return unless raw_path
57
+ return @absolute_url if defined? @absolute_url
58
+ @absolute_url = if raw_path.is_a?(String) && absolute_url?(raw_path) == false
59
+ filters.absolute_url raw_path
60
+ else
61
+ raw_path
62
+ end
63
+ end
64
+
65
+ def filters
66
+ @filters ||= Jekyll::SeoTag::Filters.new(context)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,8 +1,8 @@
1
1
  module Jekyll
2
2
  class SeoTag
3
+ # This module is deprecated, but is included in the Gem to avoid a breaking
4
+ # change and should be removed at the next major version bump
3
5
  module JSONLD
4
-
5
- # A hash of instance methods => key in resulting JSON-LD hash
6
6
  METHODS_KEYS = {
7
7
  :json_context => "@context",
8
8
  :type => "@type",
@@ -19,60 +19,10 @@ module Jekyll
19
19
  :canonical_url => "url",
20
20
  }.freeze
21
21
 
22
+ # Self should be a Jekyll::SeoTag::Drop instance (when extending the module)
22
23
  def json_ld
23
- @json_ld ||= begin
24
- output = {}
25
- METHODS_KEYS.each do |method, key|
26
- value = send(method)
27
- output[key] = value unless value.nil?
28
- end
29
- output
30
- end
31
- end
32
-
33
- private
34
-
35
- def json_context
36
- "http://schema.org"
37
- end
38
-
39
- def json_author
40
- return unless author
41
- {
42
- "@type" => "Person",
43
- "name" => author["name"],
44
- }
45
- end
46
-
47
- def json_image
48
- return unless image
49
- return image["path"] if image.length == 1
50
-
51
- hash = image.dup
52
- hash["url"] = hash.delete("path")
53
- hash["@type"] = "imageObject"
54
- hash
55
- end
56
-
57
- def publisher
58
- return unless logo
59
- output = {
60
- "@type" => "Organization",
61
- "logo" => {
62
- "@type" => "ImageObject",
63
- "url" => logo,
64
- },
65
- }
66
- output["name"] = author["name"] if author
67
- output
68
- end
69
-
70
- def main_entity
71
- return unless %w(BlogPosting CreativeWork).include?(type)
72
- {
73
- "@type" => "WebPage",
74
- "@id" => canonical_url,
75
- }
24
+ Jekyll.logger.warn "Jekyll::SeoTag::JSONLD is deprecated"
25
+ @json_ld ||= JSONLDDrop.new(self)
76
26
  end
77
27
  end
78
28
  end
@@ -0,0 +1,79 @@
1
+ module Jekyll
2
+ class SeoTag
3
+ class JSONLDDrop < Jekyll::Drops::Drop
4
+ extend Forwardable
5
+
6
+ def_delegator :page_drop, :name, :name
7
+ def_delegator :page_drop, :description, :description
8
+ def_delegator :page_drop, :canonical_url, :url
9
+ def_delegator :page_drop, :page_title, :headline
10
+ def_delegator :page_drop, :date_modified, :dateModified
11
+ def_delegator :page_drop, :date_published, :datePublished
12
+ def_delegator :page_drop, :links, :sameAs
13
+ def_delegator :page_drop, :logo, :logo
14
+ def_delegator :page_drop, :type, :type
15
+
16
+ # Expose #type and #logo as private methods and #@type as a public method
17
+ alias_method :"@type", :type
18
+ private :type
19
+ private :logo
20
+
21
+ # page_drop should be an instance of Jekyll::SeoTag::Drop
22
+ def initialize(page_drop)
23
+ @mutations = {}
24
+ @page_drop = page_drop
25
+ end
26
+
27
+ def fallback_data
28
+ {
29
+ "@context" => "http://schema.org",
30
+ }
31
+ end
32
+
33
+ def author
34
+ return unless page_drop.author["name"]
35
+ {
36
+ "@type" => "Person",
37
+ "name" => page_drop.author["name"],
38
+ }
39
+ end
40
+
41
+ def image
42
+ return unless page_drop.image
43
+ return page_drop.image.path if page_drop.image.keys.length == 1
44
+
45
+ hash = page_drop.image.to_h
46
+ hash["url"] = hash.delete("path")
47
+ hash["@type"] = "imageObject"
48
+ hash
49
+ end
50
+
51
+ def publisher
52
+ return unless logo
53
+ output = {
54
+ "@type" => "Organization",
55
+ "logo" => {
56
+ "@type" => "ImageObject",
57
+ "url" => logo,
58
+ },
59
+ }
60
+ output["name"] = page_drop.author.name if page_drop.author.name
61
+ output
62
+ end
63
+
64
+ def main_entity
65
+ return unless %w(BlogPosting CreativeWork).include?(type)
66
+ {
67
+ "@type" => "WebPage",
68
+ "@id" => page_drop.canonical_url,
69
+ }
70
+ end
71
+ alias_method :mainEntityOfPage, :main_entity
72
+ private :main_entity
73
+
74
+ private
75
+
76
+ attr_reader :page_drop
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,20 @@
1
+ module Jekyll
2
+ class SeoTag
3
+ # Mixin to share common URL-related methods between class
4
+ module UrlHelper
5
+ private
6
+
7
+ # Determines if the given string is an absolute URL
8
+ #
9
+ # Returns true if an absolute URL.
10
+ # Retruns false if it's a relative URL
11
+ # Returns nil if it is not a string or can't be parsed as a URL
12
+ def absolute_url?(string)
13
+ return unless string
14
+ Addressable::URI.parse(string).absolute?
15
+ rescue Addressable::URI::InvalidURIError
16
+ nil
17
+ end
18
+ end
19
+ end
20
+ end
@@ -3,6 +3,6 @@ module Liquid; class Tag; end; end
3
3
 
4
4
  module Jekyll
5
5
  class SeoTag < Liquid::Tag
6
- VERSION = "2.2.3".freeze
6
+ VERSION = "2.3.0".freeze
7
7
  end
8
8
  end
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ bundle exec jekyll serve --source docs
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-seo-tag
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.3
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Balter
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-05-06 00:00:00.000000000 Z
11
+ date: 2017-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -94,17 +94,27 @@ files:
94
94
  - Gemfile
95
95
  - History.markdown
96
96
  - LICENSE.txt
97
- - README.md
97
+ - docs/README.md
98
+ - docs/_config.yml
99
+ - docs/_layouts/default.html
100
+ - docs/advanced-usage.md
101
+ - docs/installation.md
102
+ - docs/usage.md
98
103
  - jekyll-seo-tag.gemspec
99
104
  - lib/jekyll-seo-tag.rb
105
+ - lib/jekyll-seo-tag/author_drop.rb
100
106
  - lib/jekyll-seo-tag/drop.rb
101
107
  - lib/jekyll-seo-tag/filters.rb
108
+ - lib/jekyll-seo-tag/image_drop.rb
102
109
  - lib/jekyll-seo-tag/json_ld.rb
110
+ - lib/jekyll-seo-tag/json_ld_drop.rb
111
+ - lib/jekyll-seo-tag/url_helper.rb
103
112
  - lib/jekyll-seo-tag/version.rb
104
113
  - lib/template.html
105
114
  - script/bootstrap
106
115
  - script/cibuild
107
116
  - script/release
117
+ - script/site
108
118
  homepage: https://github.com/benbalter/jekyll-seo-tag
109
119
  licenses:
110
120
  - MIT
data/README.md DELETED
@@ -1,226 +0,0 @@
1
- # Jekyll SEO Tag
2
-
3
- A Jekyll plugin to add metadata tags for search engines and social networks to better index and display your site's content.
4
-
5
- [![Gem Version](https://badge.fury.io/rb/jekyll-seo-tag.svg)](https://badge.fury.io/rb/jekyll-seo-tag) [![Build Status](https://travis-ci.org/jekyll/jekyll-seo-tag.svg)](https://travis-ci.org/jekyll/jekyll-seo-tag)
6
-
7
- ## What it does
8
-
9
- Jekyll SEO Tag adds the following meta tags to your site:
10
-
11
- * Page title, with site title or description appended
12
- * Page description
13
- * Canonical URL
14
- * Next and previous URLs on paginated pages
15
- * [JSON-LD Site and post metadata](https://developers.google.com/structured-data/) for richer indexing
16
- * [Open Graph](http://ogp.me/) title, description, site title, and URL (for Facebook, LinkedIn, etc.)
17
- * [Twitter Summary Card](https://dev.twitter.com/cards/overview) metadata
18
-
19
- While you could theoretically add the necessary metadata tags yourself, Jekyll SEO Tag provides a battle-tested template of crowdsourced best-practices.
20
-
21
- ## What it doesn't do
22
-
23
- Jekyll SEO tag is designed to output machine-readable metadata for search engines and social networks to index and display. If you're looking for something to analyze your Jekyll site's structure and content (e.g., more traditional SEO optimization), take a look at [The Jekyll SEO Gem](https://github.com/pmarsceill/jekyll-seo-gem).
24
-
25
- Jekyll SEO tag isn't designed to accommodate every possible use case. It should work for most site out of the box and without a laundry list of configuration options that serve only to confuse most users.
26
-
27
- ## Installation
28
-
29
- 1. Add the following to your site's `Gemfile`:
30
-
31
- ```ruby
32
- gem 'jekyll-seo-tag'
33
- ```
34
-
35
- 2. Add the following to your site's `_config.yml`:
36
-
37
- ```yml
38
- gems:
39
- - jekyll-seo-tag
40
- ```
41
-
42
- 3. Add the following right before `</head>` in your site's template(s):
43
-
44
- ```liquid
45
- {% seo %}
46
- ```
47
-
48
- ## Usage
49
-
50
- The SEO tag will respect any of the following if included in your site's `_config.yml` (and simply not include them if they're not defined):
51
-
52
- * `title` - Your site's title (e.g., Ben's awesome site, The GitHub Blog, etc.)
53
- * `description` - A short description (e.g., A blog dedicated to reviewing cat gifs)
54
- * `url` - The full URL to your site. Note: `site.github.url` will be used by default.
55
- * `author` - global author information (see below)
56
- * `twitter:username` - The site's Twitter handle. You'll want to describe it like so:
57
-
58
- ```yml
59
- twitter:
60
- username: benbalter
61
- ```
62
-
63
- * `facebook` - The following properties are available:
64
- * `facebook:app_id` - a Facebook app ID for Facebook insights
65
- * `facebook:publisher` - a Facebook page URL or ID of the publishing entity
66
- * `facebook:admins` - a Facebook user ID for domain insights linked to a personal account
67
-
68
- You'll want to describe one or more like so:
69
-
70
- ```yml
71
- facebook:
72
- app_id: 1234
73
- publisher: 1234
74
- admins: 1234
75
- ```
76
-
77
- * `logo` - URL to a site-wide logo (e.g., `/assets/your-company-logo.png`)
78
- * `social` - For [specifying social profiles](https://developers.google.com/structured-data/customize/social-profiles). The following properties are available:
79
- * `name` - If the user or organization name differs from the site's name
80
- * `links` - An array of links to social media profiles.
81
- * `date_modified` - Manually specify the `dateModified` field in the JSON-LD output to override Jekyll's own `dateModified`. This field will take **first priority** for the `dateModified` JSON-LD output. This is useful when the file timestamp does not match the true time that the content was modified. A user may also install [Last Modified At](https://github.com/gjtorikian/jekyll-last-modified-at) which will offer an alternative way of providing for the `dateModified` field.
82
-
83
- ```yml
84
- social:
85
- name: Ben Balter
86
- links:
87
- - https://twitter.com/BenBalter
88
- - https://www.facebook.com/ben.balter
89
- - https://www.linkedin.com/in/BenBalter
90
- - https://plus.google.com/+BenBalter
91
- - https://github.com/benbalter
92
- - https://keybase.io/benbalter
93
- ```
94
-
95
- * `google_site_verification` for verifying ownership via Google webmaster tools
96
- * Alternatively, verify ownership with several services at once using the following format:
97
-
98
- ```yml
99
- webmaster_verifications:
100
- google: 1234
101
- bing: 1234
102
- alexa: 1234
103
- yandex: 1234
104
- ```
105
-
106
- * `lang` - The locale these tags are marked up in. Of the format `language_TERRITORY`. Default is `en_US`.
107
-
108
- The SEO tag will respect the following YAML front matter if included in a post, page, or document:
109
-
110
- * `title` - The title of the post, page, or document
111
- * `description` - A short description of the page's content
112
- * `image` - URL to an image associated with the post, page, or document (e.g., `/assets/page-pic.jpg`)
113
- * `author` - Page-, post-, or document-specific author information (see below)
114
- * `lang` - Page-, post-, or document-specific language information
115
-
116
- ## Advanced usage
117
-
118
- Jekyll SEO Tag is designed to implement SEO best practices by default and to be the right fit for most sites right out of the box. If for some reason, you need more control over the output, read on:
119
-
120
- ### Disabling `<title>` output
121
-
122
- If for some reason, you don't want the plugin to output `<title>` tags on each page, simply invoke the plugin within your template like so:
123
-
124
- ```
125
- {% seo title=false %}
126
- ```
127
-
128
- ### Author information
129
-
130
- Author information is used to propagate the `creator` field of Twitter summary cards. This should be an author-specific, not site-wide Twitter handle (the site-wide username be stored as `site.twitter.username`).
131
-
132
- *TL;DR: In most cases, put `author: [your Twitter handle]` in the document's front matter, for sites with multiple authors. If you need something more complicated, read on.*
133
-
134
- There are several ways to convey this author-specific information. Author information is found in the following order of priority:
135
-
136
- 1. An `author` object, in the documents's front matter, e.g.:
137
-
138
- ```yml
139
- author:
140
- twitter: benbalter
141
- ```
142
-
143
- 2. An `author` object, in the site's `_config.yml`, e.g.:
144
-
145
- ```yml
146
- author:
147
- twitter: benbalter
148
- ```
149
-
150
- 3. `site.data.authors[author]`, if an author is specified in the document's front matter, and a corresponding key exists in `site.data.authors`. E.g., you have the following in the document's front matter:
151
-
152
- ```yml
153
- author: benbalter
154
- ```
155
-
156
- And you have the following in `_data/authors.yml`:
157
-
158
- ```yml
159
- benbalter:
160
- picture: /img/benbalter.png
161
- twitter: jekyllrb
162
-
163
- potus:
164
- picture: /img/potus.png
165
- twitter: whitehouse
166
- ```
167
-
168
- In the above example, the author `benbalter`'s Twitter handle will be resolved to `@jekyllrb`. This allows you to centralize author information in a single `_data/authors` file for site with many authors that require more than just the author's username.
169
-
170
- *Pro-tip: If `authors` is present in the document's front matter as an array (and `author` is not), the plugin will use the first author listed, as Twitter supports only one author.*
171
-
172
- 4. An author in the document's front matter (the simplest way), e.g.:
173
-
174
- ```yml
175
- author: benbalter
176
- ```
177
-
178
- 5. An author in the site's `_config.yml`, e.g.:
179
-
180
- ```yml
181
- author: benbalter
182
- ```
183
-
184
- ### Customizing JSON-LD output
185
-
186
- The following options can be set for any particular page. While the default options are meant to serve most users in the most common circumstances, there may be situations where more precise control is necessary.
187
-
188
- * `seo`
189
- * `name` - If the name of the thing that the page represents is different from the page title. (i.e.: "Frank's Café" vs "Welcome to Frank's Café")
190
- * `type` - The type of things that the page represents. This must be a [Schema.org type](http://schema.org/docs/schemas.html), and will probably usually be something like [`BlogPosting`](http://schema.org/BlogPosting), [`NewsArticle`](http://schema.org/NewsArticle), [`Person`](http://schema.org/Person), [`Organization`](http://schema.org/Organization), etc.
191
- * `links` - An array of other URLs that represent the same thing that this page represents. For instance, Jane's bio page might include links to Jane's GitHub and Twitter profiles.
192
-
193
- ### Customizing image output
194
-
195
- For most users, setting `image: [path-to-image]` on a per-page basis should be enough. If you need more control over how images are represented, the `image` property can also be an object, with the following options:
196
-
197
- * `path` - The relative path to the image. Same as `image: [path-to-image]`
198
- * `height` - The height of the Open Graph (`og:image`) image
199
- * `width` - The width of the Open Graph (`og:image`) image
200
-
201
- You can use any of the above, optional properties, like so:
202
-
203
- ```yml
204
- image:
205
- path: /img/twitter.png
206
- height: 100
207
- width: 100
208
- ```
209
-
210
- ### Setting a default image
211
-
212
- You can define a default image using [Front Matter default](https://jekyllrb.com/docs/configuration/#front-matter-defaults), to provide a default Twitter Card or OGP image to all of your posts and pages.
213
-
214
- Here is a very basic example, that you are encouraged to adapt to your needs:
215
-
216
- ```yml
217
- defaults:
218
- - scope:
219
- path: ""
220
- values:
221
- image: /assets/images/default-card.png
222
- ```
223
-
224
- ### SmartyPants Titles
225
-
226
- Titles will be processed using [Jekyll's `smartify` filter](https://jekyllrb.com/docs/templates/). This will use SmartyPants to translate plain ASCII punctuation into "smart" typographic punctuation. This will not render or strip any Markdown you may be using in a page title.