meta-tags 1.6.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/CHANGELOG.md +10 -0
- data/README.md +19 -2
- data/Rakefile +10 -0
- data/lib/meta_tags.rb +8 -1
- data/lib/meta_tags/content_tag.rb +14 -0
- data/lib/meta_tags/controller_helper.rb +25 -29
- data/lib/meta_tags/meta_tags_collection.rb +181 -0
- data/lib/meta_tags/renderer.rb +176 -0
- data/lib/meta_tags/tag.rb +25 -0
- data/lib/meta_tags/text_normalizer.rb +83 -0
- data/lib/meta_tags/version.rb +2 -1
- data/lib/meta_tags/view_helper.rb +15 -164
- data/spec/controller_helper_spec.rb +1 -1
- data/spec/meta_tags_spec.rb +28 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NmY1YmU5YTM0ZjQwYzBkODRkZmViODlmMTI1NjljYzdkMzcwMzljYg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
OGQ1ZjE5YzFjMjcxNzM1OTliZDNhY2MxMDU0M2ExMTkxOTMzZTkxMQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MjA0OTRkMGJjMDc1ZGE1NDk5OTExZGRmMDVhZjNiZDFmMTgzYjYxYTYzOWYw
|
10
|
+
N2YxYjc4YjI4ZDc3ZjIyYjBmNWJiNjBhNzI2MmY3MmRlMDliMjU2ZTQ5Y2My
|
11
|
+
MmM2YTJmZjUzMzFjYWFlMDQxMzJlZmYxMWVkZTdlYjFjMjAwY2U=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
M2FmMmJlZGZhNTU0YWIwMTUzYzZhNGY5OWQxNDA4MGYyOTZlOTUzMzM4NTg4
|
14
|
+
YzU4MGVhZTNiNzk0NzYwNDQ5NTUwYjM2NTA5MTA3YjQxMzI1MWVhZGUwY2Vl
|
15
|
+
NjY5MjVlNTZhYmY1MjNhNjA1MWQ0ZjVkMTU2NjFkZWE2OTI4NWI=
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# MetaTags: a gem to make your Rails application SEO-friendly
|
2
2
|
|
3
3
|
[![Travis-CI build status](https://secure.travis-ci.org/kpumuk/meta-tags.png)](http://travis-ci.org/kpumuk/meta-tags)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/meta-tags.svg)](http://badge.fury.io/rb/meta-tags)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/kpumuk/meta-tags.png)](https://codeclimate.com/github/kpumuk/meta-tags)
|
4
6
|
|
5
7
|
Search Engine Optimization (SEO) plugin for Ruby on Rails applications.
|
6
8
|
|
@@ -240,11 +242,26 @@ There are 3 card types (summary, photo and player). Here's an example for summar
|
|
240
242
|
:card => "summary",
|
241
243
|
:site => "@username"
|
242
244
|
}
|
243
|
-
# <meta
|
244
|
-
# <meta
|
245
|
+
# <meta name="twitter:card" content="summary"/>
|
246
|
+
# <meta name="twitter:site" content="@username"/>
|
245
247
|
|
246
248
|
Take in consideration that if you're already using OpenGraph to describe data on your page, it’s easy to generate a Twitter card without duplicating your tags and data. When the Twitter card processor looks for tags on your page, it first checks for the Twitter property, and if not present, falls back to the supported Open Graph property. This allows for both to be defined on the page independently, and minimizes the amount of duplicate markup required to describe your content and experience.
|
247
249
|
|
250
|
+
When you need to generate a [Twitter Photo card](https://dev.twitter.com/docs/cards/types/photo-card), `twitter:image` property is a string, while image dimensions are specified using `twitter:image:width` and `twitter:image:height`, or a `Hash` objects in terms of MetaTags gems. There is a special syntax to make this work:
|
251
|
+
|
252
|
+
set_meta_tags :twitter => {
|
253
|
+
:card => "photo",
|
254
|
+
:image => {
|
255
|
+
:_ => "http://example.com/1.png",
|
256
|
+
:width => 100,
|
257
|
+
:height => 100,
|
258
|
+
}
|
259
|
+
}
|
260
|
+
# <meta name="twitter:card" content="photo"/>
|
261
|
+
# <meta name="twitter:image" content="http://example.com/1.png"/>
|
262
|
+
# <meta name="twitter:image:width" content="100"/>
|
263
|
+
# <meta name="twitter:image:height" content="100"/>
|
264
|
+
|
248
265
|
Further reading:
|
249
266
|
|
250
267
|
* [Twitter Cards Documentation](https://dev.twitter.com/docs/cards/)
|
data/Rakefile
CHANGED
@@ -7,6 +7,16 @@ RSpec::Core::RakeTask.new(:spec)
|
|
7
7
|
task :test => :spec
|
8
8
|
task :default => :spec
|
9
9
|
|
10
|
+
desc 'Starts irb with MetaTags gem loaded'
|
11
|
+
task :console do
|
12
|
+
require 'irb'
|
13
|
+
|
14
|
+
$:.unshift File.expand_path('../lib', __FILE__)
|
15
|
+
require 'meta_tags'
|
16
|
+
ARGV.clear
|
17
|
+
IRB.start
|
18
|
+
end
|
19
|
+
|
10
20
|
require 'yard'
|
11
21
|
YARD::Rake::YardocTask.new(:yard) do |t|
|
12
22
|
t.options = ['--title', 'MetaTags Documentation']
|
data/lib/meta_tags.rb
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
require 'action_controller'
|
2
2
|
require 'action_view'
|
3
3
|
|
4
|
+
# MetaTags gem namespace.
|
4
5
|
module MetaTags
|
5
6
|
end
|
6
7
|
|
7
8
|
require 'meta_tags/version'
|
8
|
-
|
9
|
+
|
9
10
|
require 'meta_tags/controller_helper'
|
11
|
+
require 'meta_tags/meta_tags_collection'
|
12
|
+
require 'meta_tags/renderer'
|
13
|
+
require 'meta_tags/tag'
|
14
|
+
require 'meta_tags/content_tag'
|
15
|
+
require 'meta_tags/text_normalizer'
|
16
|
+
require 'meta_tags/view_helper'
|
10
17
|
|
11
18
|
ActionView::Base.send :include, MetaTags::ViewHelper
|
12
19
|
ActionController::Base.send :include, MetaTags::ControllerHelper
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module MetaTags
|
2
|
+
# Represents an HTML meta tag with content (<tag></tag>).
|
3
|
+
# Content should be passed as a `:content` attribute.
|
4
|
+
class ContentTag < Tag
|
5
|
+
# Render tag into a Rails view.
|
6
|
+
#
|
7
|
+
# @param [ActionView::Base] view instance of a Rails view.
|
8
|
+
# @return [String] HTML string for the tag.
|
9
|
+
#
|
10
|
+
def render(view)
|
11
|
+
view.content_tag(name, attributes[:content], attributes.except(:content))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -6,43 +6,39 @@ module MetaTags
|
|
6
6
|
# @page_description = 'Member login page.'
|
7
7
|
# @page_keywords = 'Site, Login, Members'
|
8
8
|
#
|
9
|
-
# Also you can use {
|
9
|
+
# Also you can use {#set_meta_tags} method, that have the same parameters
|
10
10
|
# as {ViewHelper#set_meta_tags}.
|
11
11
|
#
|
12
12
|
module ControllerHelper
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
|
15
|
+
included do
|
16
|
+
alias_method_chain :render, :meta_tags
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
meta_tags[:keywords] = @page_keywords if @page_keywords
|
25
|
-
meta_tags[:description] = @page_description if @page_description
|
26
|
-
set_meta_tags(meta_tags)
|
19
|
+
# Processes the <tt>@page_title</tt>, <tt>@page_keywords</tt>, and
|
20
|
+
# <tt>@page_description</tt> instance variables and calls +render+.
|
21
|
+
def render_with_meta_tags(*args, &block)
|
22
|
+
self.meta_tags[:title] = @page_title if @page_title
|
23
|
+
self.meta_tags[:keywords] = @page_keywords if @page_keywords
|
24
|
+
self.meta_tags[:description] = @page_description if @page_description
|
27
25
|
|
28
|
-
|
29
|
-
|
26
|
+
render_without_meta_tags(*args, &block)
|
27
|
+
end
|
28
|
+
protected :render_with_meta_tags
|
30
29
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
protected :set_meta_tags
|
30
|
+
# Set meta tags for the page.
|
31
|
+
#
|
32
|
+
# See <tt>MetaTags::ViewHelper#set_meta_tags</tt> for details.
|
33
|
+
def set_meta_tags(meta_tags)
|
34
|
+
self.meta_tags.update(meta_tags)
|
35
|
+
end
|
36
|
+
protected :set_meta_tags
|
40
37
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
45
|
-
protected :meta_tags
|
38
|
+
# Get meta tags for the page.
|
39
|
+
def meta_tags
|
40
|
+
@meta_tags ||= MetaTagsCollection.new
|
46
41
|
end
|
42
|
+
protected :meta_tags
|
47
43
|
end
|
48
44
|
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
module MetaTags
|
2
|
+
# Class represents a collection of meta tags. Basically a wrapper around
|
3
|
+
# HashWithIndifferentAccess, with some additional helper methods.
|
4
|
+
class MetaTagsCollection
|
5
|
+
attr_reader :meta_tags
|
6
|
+
|
7
|
+
# Initializes a new instance of MetaTagsCollection.
|
8
|
+
#
|
9
|
+
def initialize
|
10
|
+
@meta_tags = HashWithIndifferentAccess.new
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns meta tag value by name.
|
14
|
+
#
|
15
|
+
# @param [String, Symbol] name meta tag name.
|
16
|
+
# @return meta tag value.
|
17
|
+
#
|
18
|
+
def [](name)
|
19
|
+
@meta_tags[name]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets meta tag value by name.
|
23
|
+
#
|
24
|
+
# @param [String, Symbol] name meta tag name.
|
25
|
+
# @param value meta tag value.
|
26
|
+
# @return meta tag value.
|
27
|
+
#
|
28
|
+
def []=(name, value)
|
29
|
+
@meta_tags[name] = value
|
30
|
+
end
|
31
|
+
|
32
|
+
# Recursively merges a Hash of meta tag attributes into current list.
|
33
|
+
#
|
34
|
+
# @param [Hash] meta_tags meta tags Hash to merge into current list.
|
35
|
+
# @return [Hash] result of the merge.
|
36
|
+
#
|
37
|
+
def update(meta_tags = {})
|
38
|
+
@meta_tags.deep_merge! normalize_open_graph(meta_tags)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Temporary merges defaults with current meta tags list and yields the block.
|
42
|
+
#
|
43
|
+
# @param [Hash] defaults list of default meta tag attributes.
|
44
|
+
# @return result of the block call.
|
45
|
+
#
|
46
|
+
def with_defaults(defaults = {})
|
47
|
+
old_meta_tags = @meta_tags
|
48
|
+
@meta_tags = normalize_open_graph(defaults).deep_merge!(self.meta_tags)
|
49
|
+
yield
|
50
|
+
ensure
|
51
|
+
@meta_tags = old_meta_tags
|
52
|
+
end
|
53
|
+
|
54
|
+
# Constructs the full title as if it would be rendered in title meta tag.
|
55
|
+
#
|
56
|
+
# @param [Hash] defaults list of default meta tag attributes.
|
57
|
+
# @return [String] page title.
|
58
|
+
#
|
59
|
+
def full_title(defaults = {})
|
60
|
+
with_defaults(defaults) { extract_full_title }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Deletes and returns a meta tag value by name.
|
64
|
+
#
|
65
|
+
# @param [String, Symbol] name meta tag name.
|
66
|
+
# @return [Object] meta tag value.
|
67
|
+
#
|
68
|
+
def extract(name)
|
69
|
+
@meta_tags.delete(name)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Deletes specified meta tags.
|
73
|
+
#
|
74
|
+
# @param [Array<String, Symbol>] names a list of meta tags to delete.
|
75
|
+
#
|
76
|
+
def delete(*names)
|
77
|
+
names.each { |name| @meta_tags.delete(name) }
|
78
|
+
end
|
79
|
+
|
80
|
+
# Extracts full page title and deletes all related meta tags.
|
81
|
+
#
|
82
|
+
# @return [String] page title.
|
83
|
+
#
|
84
|
+
def extract_full_title
|
85
|
+
title = extract_title || []
|
86
|
+
separator = extract_separator
|
87
|
+
|
88
|
+
site_title = extract(:site).presence
|
89
|
+
title.unshift(site_title) if site_title
|
90
|
+
title = TextNormalizer.normalize_title(title)
|
91
|
+
|
92
|
+
title.reverse! if extract(:reverse) === true
|
93
|
+
title = TextNormalizer.safe_join(title, separator)
|
94
|
+
|
95
|
+
title
|
96
|
+
end
|
97
|
+
|
98
|
+
# Extracts page title as an array of segments without site title and separators.
|
99
|
+
#
|
100
|
+
# @return [Array<String>] segments of page title.
|
101
|
+
#
|
102
|
+
def extract_title
|
103
|
+
title = extract(:title).presence
|
104
|
+
return unless title
|
105
|
+
|
106
|
+
title = Array(title)
|
107
|
+
title.each(&:downcase!) if extract(:lowercase) === true
|
108
|
+
title
|
109
|
+
end
|
110
|
+
|
111
|
+
# Extracts title separator as a string.
|
112
|
+
#
|
113
|
+
# @return [String] page title separator.
|
114
|
+
#
|
115
|
+
def extract_separator
|
116
|
+
if meta_tags[:separator] === false
|
117
|
+
# Special case: if separator is hidden, do not display suffix/prefix
|
118
|
+
prefix = separator = suffix = ''
|
119
|
+
else
|
120
|
+
prefix = extract_separator_section(:prefix, ' ')
|
121
|
+
separator = extract_separator_section(:separator, '|')
|
122
|
+
suffix = extract_separator_section(:suffix, ' ')
|
123
|
+
end
|
124
|
+
delete(:separator, :prefix, :suffix)
|
125
|
+
|
126
|
+
TextNormalizer.safe_join([prefix, separator, suffix], '')
|
127
|
+
end
|
128
|
+
|
129
|
+
# Extracts noindex settings as a Hash mapping noindex tag name to value.
|
130
|
+
#
|
131
|
+
# @return [Hash<String,String>] noindex attributes.
|
132
|
+
#
|
133
|
+
def extract_noindex
|
134
|
+
noindex_name, noindex_value = extract_noindex_attribute(:noindex)
|
135
|
+
nofollow_name, nofollow_value = extract_noindex_attribute(:nofollow)
|
136
|
+
|
137
|
+
if noindex_name == nofollow_name
|
138
|
+
{ noindex_name => [noindex_value, nofollow_value].compact.join(', ') }
|
139
|
+
else
|
140
|
+
{ noindex_name => noindex_value, nofollow_name => nofollow_value }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
protected
|
145
|
+
|
146
|
+
# Converts input hash to HashWithIndifferentAccess and renames :open_graph to :og.
|
147
|
+
#
|
148
|
+
# @param [Hash] meta_tags list of meta tags.
|
149
|
+
# @return [HashWithIndifferentAccess] normalized meta tags list.
|
150
|
+
#
|
151
|
+
def normalize_open_graph(meta_tags)
|
152
|
+
meta_tags = meta_tags.is_a?(HashWithIndifferentAccess) ? meta_tags.dup : meta_tags.with_indifferent_access
|
153
|
+
meta_tags[:og] = meta_tags.delete(:open_graph) if meta_tags.key?(:open_graph)
|
154
|
+
meta_tags
|
155
|
+
end
|
156
|
+
|
157
|
+
# Extracts separator segment without deleting it from meta tags list.
|
158
|
+
# If the value is false, empty string will be returned.
|
159
|
+
#
|
160
|
+
# @param [Symbol, String] name separator segment name.
|
161
|
+
# @param [String] default default value.
|
162
|
+
# @return [String] separator segment value.
|
163
|
+
#
|
164
|
+
def extract_separator_section(name, default)
|
165
|
+
meta_tags[name] === false ? '' : (meta_tags[name] || default)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Extracts noindex attribute name and value without deleting it from meta tags list.
|
169
|
+
#
|
170
|
+
# @param [String, Symbol] name noindex attribute name.
|
171
|
+
# @return [Array<String>] pair of noindex attribute name and value.
|
172
|
+
#
|
173
|
+
def extract_noindex_attribute(name)
|
174
|
+
noindex = extract(name)
|
175
|
+
noindex_name = String === noindex ? noindex : 'robots'
|
176
|
+
noindex_value = noindex ? name.to_s : nil
|
177
|
+
|
178
|
+
[ noindex_name, noindex_value ]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
module MetaTags
|
2
|
+
# This class is used by MetaTags gems to render HTML meta tags into page.
|
3
|
+
class Renderer
|
4
|
+
attr_reader :meta_tags, :normalized_meta_tags
|
5
|
+
|
6
|
+
# Initialized a new instance of Renderer.
|
7
|
+
#
|
8
|
+
# @param [MetaTagsCollection] meta_tags meta tags object to render.
|
9
|
+
#
|
10
|
+
def initialize(meta_tags)
|
11
|
+
@meta_tags = meta_tags
|
12
|
+
@normalized_meta_tags = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# Renders meta tags on the page.
|
16
|
+
#
|
17
|
+
# @param [ActionView::Base] view Rails view object.
|
18
|
+
def render(view)
|
19
|
+
tags = []
|
20
|
+
|
21
|
+
render_title(tags)
|
22
|
+
render_description(tags)
|
23
|
+
render_keywords(tags)
|
24
|
+
render_refresh(tags)
|
25
|
+
render_noindex(tags)
|
26
|
+
render_alternate(tags)
|
27
|
+
render_links(tags)
|
28
|
+
|
29
|
+
render_hash(tags, :twitter, :name_key => :name)
|
30
|
+
render_hashes(tags)
|
31
|
+
render_custom(tags)
|
32
|
+
|
33
|
+
tags.compact.map { |tag| tag.render(view) }.join("\n").html_safe
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
# Renders title tag.
|
39
|
+
#
|
40
|
+
# @param [Array<Tag>] tags a buffer object to store tag in.
|
41
|
+
#
|
42
|
+
def render_title(tags)
|
43
|
+
title = meta_tags.extract_full_title
|
44
|
+
normalized_meta_tags[:title] = title
|
45
|
+
tags << ContentTag.new(:title, :content => title) if title.present?
|
46
|
+
end
|
47
|
+
|
48
|
+
# Renders description meta tag.
|
49
|
+
#
|
50
|
+
# @param [Array<Tag>] tags a buffer object to store tag in.
|
51
|
+
#
|
52
|
+
def render_description(tags)
|
53
|
+
description = TextNormalizer.normalize_description(meta_tags.extract(:description))
|
54
|
+
normalized_meta_tags[:description] = description
|
55
|
+
tags << Tag.new(:meta, :name => :description, :content => description) if description.present?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Renders keywords meta tag.
|
59
|
+
#
|
60
|
+
# @param [Array<Tag>] tags a buffer object to store tag in.
|
61
|
+
#
|
62
|
+
def render_keywords(tags)
|
63
|
+
keywords = TextNormalizer.normalize_keywords(meta_tags.extract(:keywords))
|
64
|
+
normalized_meta_tags[:keywords] = keywords
|
65
|
+
tags << Tag.new(:meta, :name => :keywords, :content => keywords) if keywords.present?
|
66
|
+
end
|
67
|
+
|
68
|
+
# Renders noindex and nofollow meta tags.
|
69
|
+
#
|
70
|
+
# @param [Array<Tag>] tags a buffer object to store tag in.
|
71
|
+
#
|
72
|
+
def render_noindex(tags)
|
73
|
+
meta_tags.extract_noindex.each do |name, content|
|
74
|
+
tags << Tag.new(:meta, :name => name, :content => content) if content.present?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Renders refresh meta tag.
|
79
|
+
#
|
80
|
+
# @param [Array<Tag>] tags a buffer object to store tag in.
|
81
|
+
#
|
82
|
+
def render_refresh(tags)
|
83
|
+
if refresh = meta_tags.extract(:refresh)
|
84
|
+
tags << Tag.new(:meta, 'http-equiv' => 'refresh', :content => refresh.to_s) if refresh.present?
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Renders alternate link tags.
|
89
|
+
#
|
90
|
+
# @param [Array<Tag>] tags a buffer object to store tag in.
|
91
|
+
#
|
92
|
+
def render_alternate(tags)
|
93
|
+
if alternate = meta_tags.extract(:alternate)
|
94
|
+
alternate.each do |hreflang, href|
|
95
|
+
tags << Tag.new(:link, :rel => 'alternate', :href => href, :hreflang => hreflang) if href.present?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Renders links.
|
101
|
+
#
|
102
|
+
# @param [Array<Tag>] tags a buffer object to store tag in.
|
103
|
+
#
|
104
|
+
def render_links(tags)
|
105
|
+
[ :canonical, :prev, :next, :author, :publisher ].each do |tag_name|
|
106
|
+
href = meta_tags.extract(tag_name)
|
107
|
+
if href.present?
|
108
|
+
@normalized_meta_tags[tag_name] = href
|
109
|
+
tags << Tag.new(:link, :rel => tag_name, :href => href)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Renders complex hash objects.
|
115
|
+
#
|
116
|
+
# @param [Array<Tag>] tags a buffer object to store tag in.
|
117
|
+
#
|
118
|
+
def render_hashes(tags, options = {})
|
119
|
+
meta_tags.meta_tags.each do |property, data|
|
120
|
+
if data.is_a?(Hash)
|
121
|
+
process_tree(tags, property, data)
|
122
|
+
meta_tags.extract(property)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Renders a complex hash object by key.
|
128
|
+
#
|
129
|
+
# @param [Array<Tag>] tags a buffer object to store tag in.
|
130
|
+
#
|
131
|
+
def render_hash(tags, key, options = {})
|
132
|
+
data = meta_tags.meta_tags[key]
|
133
|
+
if data.is_a?(Hash)
|
134
|
+
process_tree(tags, key, data, options)
|
135
|
+
meta_tags.extract(key)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Renders custom meta tags.
|
140
|
+
#
|
141
|
+
# @param [Array<Tag>] tags a buffer object to store tag in.
|
142
|
+
#
|
143
|
+
def render_custom(tags)
|
144
|
+
meta_tags.meta_tags.each do |name, data|
|
145
|
+
Array(data).each do |val|
|
146
|
+
tags << Tag.new(:meta, :name => name, :content => val)
|
147
|
+
end
|
148
|
+
meta_tags.extract(name)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Recursive method to process all the hashes and arrays on meta tags
|
153
|
+
#
|
154
|
+
# @param [Array<Tag>] tags a buffer object to store tag in.
|
155
|
+
# @param [Hash, String] property a Hash or a String to render as meta tag.
|
156
|
+
# @param [String, Symbol] content text content or a symbol reference to
|
157
|
+
# top-level meta tag.
|
158
|
+
#
|
159
|
+
def process_tree(tags, property, content, options = {})
|
160
|
+
content = [content] if content.is_a?(Hash)
|
161
|
+
Array(content).each do |c|
|
162
|
+
if c.is_a?(Hash)
|
163
|
+
c.each do |key, value|
|
164
|
+
key = key.to_s == '_' ? property : "#{property}:#{key}"
|
165
|
+
value = normalized_meta_tags[value] if value.is_a?(Symbol)
|
166
|
+
process_tree(tags, key, value, options)
|
167
|
+
end
|
168
|
+
else
|
169
|
+
name_key = options.fetch(:name_key, :property)
|
170
|
+
value_key = options.fetch(:value_key, :content)
|
171
|
+
tags << Tag.new(:meta, name_key => property.to_s, value_key => c) unless c.blank?
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module MetaTags
|
2
|
+
# Represents an HTML meta tag with no content (<tag />).
|
3
|
+
class Tag
|
4
|
+
attr_reader :name, :attributes
|
5
|
+
|
6
|
+
# Initializes a new instance of Tag class.
|
7
|
+
#
|
8
|
+
# @param [String, Symbol] name HTML tag name
|
9
|
+
# @param [Hash] attributes list of HTML tag attributes
|
10
|
+
#
|
11
|
+
def initialize(name, attributes = {})
|
12
|
+
@name = name
|
13
|
+
@attributes = attributes
|
14
|
+
end
|
15
|
+
|
16
|
+
# Render tag into a Rails view.
|
17
|
+
#
|
18
|
+
# @param [ActionView::Base] view instance of a Rails view.
|
19
|
+
# @return [String] HTML string for the tag.
|
20
|
+
#
|
21
|
+
def render(view)
|
22
|
+
view.tag(name, attributes)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module MetaTags
|
2
|
+
# Module contains helpers that normalize text meta tag values.
|
3
|
+
module TextNormalizer
|
4
|
+
# Normalize title value.
|
5
|
+
#
|
6
|
+
# @param [String, Array<String>] title title string.
|
7
|
+
# @return [Array<String>] array of title parts with tags removed.
|
8
|
+
#
|
9
|
+
def self.normalize_title(title)
|
10
|
+
Array(title).flatten.map(&method(:strip_tags))
|
11
|
+
end
|
12
|
+
|
13
|
+
# Normalize description value.
|
14
|
+
#
|
15
|
+
# @param [String] description description string.
|
16
|
+
# @return [String] text with tags removed, squashed spaces, truncated
|
17
|
+
# to 200 characters.
|
18
|
+
#
|
19
|
+
def self.normalize_description(description)
|
20
|
+
return '' if description.blank?
|
21
|
+
helpers.truncate(cleanup_string(description), :length => 200)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Normalize keywords value.
|
25
|
+
#
|
26
|
+
# @param [String, Array<String>] keywords list of keywords as a string or Array.
|
27
|
+
# @return [String] list of keywords joined with comma, with tags removed.
|
28
|
+
#
|
29
|
+
def self.normalize_keywords(keywords)
|
30
|
+
return '' if keywords.blank?
|
31
|
+
cleanup_strings(keywords).join(', ').downcase
|
32
|
+
end
|
33
|
+
|
34
|
+
# Easy way to get access to Rails helpers.
|
35
|
+
#
|
36
|
+
# @return [ActionView::Base] proxy object to access Rails helpers.
|
37
|
+
#
|
38
|
+
def self.helpers
|
39
|
+
ActionController::Base.helpers
|
40
|
+
end
|
41
|
+
|
42
|
+
# Strips all HTML tags from the +html+, including comments.
|
43
|
+
#
|
44
|
+
# @param [String] string HTML string.
|
45
|
+
# @return [String] string with no HTML tags.
|
46
|
+
#
|
47
|
+
def self.strip_tags(string)
|
48
|
+
helpers.strip_tags(string)
|
49
|
+
end
|
50
|
+
|
51
|
+
# This method returns a html safe string similar to what <tt>Array#join</tt>
|
52
|
+
# would return. All items in the array, including the supplied separator, are
|
53
|
+
# html escaped unless they are html safe, and the returned string is marked
|
54
|
+
# as html safe.
|
55
|
+
#
|
56
|
+
# @param [Array<String>] array list of strings to join.
|
57
|
+
# @param [String] sep separator to join strings with.
|
58
|
+
# @return [String] input strings joined together using a given separator.
|
59
|
+
#
|
60
|
+
def self.safe_join(array, sep = $,)
|
61
|
+
helpers.safe_join(array, sep)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Removes HTML tags and squashes down all the spaces.
|
65
|
+
#
|
66
|
+
# @param [String] string input string.
|
67
|
+
# @return [String] input string with no HTML tags and consequent white
|
68
|
+
# space characters squashed into a single space.
|
69
|
+
#
|
70
|
+
def self.cleanup_string(string)
|
71
|
+
strip_tags(string).gsub(/\s+/, ' ').strip
|
72
|
+
end
|
73
|
+
|
74
|
+
# Cleans multiple strings up.
|
75
|
+
#
|
76
|
+
# @param [Array<String>] strings input strings.
|
77
|
+
# @return [Array<String>] clean strings.
|
78
|
+
# @see cleanup_string
|
79
|
+
def self.cleanup_strings(strings)
|
80
|
+
Array(strings).flatten.map(&method(:cleanup_string))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/meta_tags/version.rb
CHANGED
@@ -4,7 +4,7 @@ module MetaTags
|
|
4
4
|
module ViewHelper
|
5
5
|
# Get meta tags for the page.
|
6
6
|
def meta_tags
|
7
|
-
@meta_tags ||=
|
7
|
+
@meta_tags ||= MetaTagsCollection.new
|
8
8
|
end
|
9
9
|
|
10
10
|
# Set meta tags for the page.
|
@@ -26,7 +26,7 @@ module MetaTags
|
|
26
26
|
# @see #display_meta_tags
|
27
27
|
#
|
28
28
|
def set_meta_tags(meta_tags = {})
|
29
|
-
self.meta_tags.
|
29
|
+
self.meta_tags.update(meta_tags)
|
30
30
|
end
|
31
31
|
|
32
32
|
# Set the page title and return it back.
|
@@ -35,9 +35,8 @@ module MetaTags
|
|
35
35
|
# and returns it (or +headline+ if specified).
|
36
36
|
#
|
37
37
|
# @param [nil, String, Array] title page title. When passed as an
|
38
|
-
# +Array+, parts will be joined
|
39
|
-
#
|
40
|
-
# title will be returned.
|
38
|
+
# +Array+, parts will be joined using configured separator value
|
39
|
+
# (see {#display_meta_tags}). When nil, current title will be returned.
|
41
40
|
# @param [String] headline the value to return from method. Useful
|
42
41
|
# for using this method in views to set both page title
|
43
42
|
# and the content of heading tag.
|
@@ -78,7 +77,7 @@ module MetaTags
|
|
78
77
|
|
79
78
|
# Set the page description.
|
80
79
|
#
|
81
|
-
# @param [String] page description to be set in HEAD section of
|
80
|
+
# @param [String] description page description to be set in HEAD section of
|
82
81
|
# the HTML document. Please note, any HTML tags will be stripped
|
83
82
|
# from output string, and string will be truncated to 200
|
84
83
|
# characters.
|
@@ -145,7 +144,7 @@ module MetaTags
|
|
145
144
|
# Set default meta tag values and display meta tags. This method
|
146
145
|
# should be used in layout file.
|
147
146
|
#
|
148
|
-
# @param [Hash]
|
147
|
+
# @param [Hash] defaults default meta tag values.
|
149
148
|
# @option default [String] :site (nil) site title;
|
150
149
|
# @option default [String] :title ("") page title;
|
151
150
|
# @option default [String] :description (nil) page description;
|
@@ -153,7 +152,7 @@ module MetaTags
|
|
153
152
|
# @option default [String, Boolean] :prefix (" ") text between site name and separator; when +false+, no prefix will be rendered;
|
154
153
|
# @option default [String] :separator ("|") text used to separate website name from page title;
|
155
154
|
# @option default [String, Boolean] :suffix (" ") text between separator and page title; when +false+, no suffix will be rendered;
|
156
|
-
# @option default [Boolean] :lowercase (false) when true, the page
|
155
|
+
# @option default [Boolean] :lowercase (false) when true, the page title will be lowercase;
|
157
156
|
# @option default [Boolean] :reverse (false) when true, the page and site names will be reversed;
|
158
157
|
# @option default [Boolean, String] :noindex (false) add noindex meta tag; when true, 'robots' will be used, otherwise the string will be used;
|
159
158
|
# @option default [Boolean, String] :nofollow (false) add nofollow meta tag; when true, 'robots' will be used, otherwise the string will be used;
|
@@ -161,6 +160,8 @@ module MetaTags
|
|
161
160
|
# @option default [Hash] :alternate ({}) add alternate link tag.
|
162
161
|
# @option default [String] :prev (nil) add prev link tag;
|
163
162
|
# @option default [String] :next (nil) add next link tag.
|
163
|
+
# @option default [String] :author (nil) add author link tag;
|
164
|
+
# @option default [String] :publisher (nil) add publisher link tag.
|
164
165
|
# @option default [String, Integer] :refresh (nil) meta refresh tag;
|
165
166
|
# @option default [Hash] :open_graph ({}) add Open Graph meta tags.
|
166
167
|
# @return [String] HTML meta tags to render in HEAD section of the
|
@@ -171,73 +172,8 @@ module MetaTags
|
|
171
172
|
# <%= display_meta_tags :site => 'My website' %>
|
172
173
|
# </head>
|
173
174
|
#
|
174
|
-
def display_meta_tags(
|
175
|
-
meta_tags
|
176
|
-
|
177
|
-
result = []
|
178
|
-
|
179
|
-
# title
|
180
|
-
title = build_full_title(meta_tags)
|
181
|
-
result << content_tag(:title, title) unless title.blank?
|
182
|
-
|
183
|
-
# description
|
184
|
-
description = normalize_description(meta_tags.delete(:description))
|
185
|
-
result << tag(:meta, :name => :description, :content => description) unless description.blank?
|
186
|
-
|
187
|
-
# keywords
|
188
|
-
keywords = normalize_keywords(meta_tags.delete(:keywords))
|
189
|
-
result << tag(:meta, :name => :keywords, :content => keywords) unless keywords.blank?
|
190
|
-
|
191
|
-
# noindex & nofollow
|
192
|
-
noindex_name = String === meta_tags[:noindex] ? meta_tags[:noindex] : 'robots'
|
193
|
-
nofollow_name = String === meta_tags[:nofollow] ? meta_tags[:nofollow] : 'robots'
|
194
|
-
|
195
|
-
if noindex_name == nofollow_name
|
196
|
-
content = [meta_tags[:noindex] && 'noindex', meta_tags[:nofollow] && 'nofollow'].compact.join(', ')
|
197
|
-
result << tag(:meta, :name => noindex_name, :content => content) unless content.blank?
|
198
|
-
else
|
199
|
-
result << tag(:meta, :name => noindex_name, :content => 'noindex') if meta_tags[:noindex] && meta_tags[:noindex] != false
|
200
|
-
result << tag(:meta, :name => nofollow_name, :content => 'nofollow') if meta_tags[:nofollow] && meta_tags[:nofollow] != false
|
201
|
-
end
|
202
|
-
meta_tags.delete(:noindex)
|
203
|
-
meta_tags.delete(:nofollow)
|
204
|
-
|
205
|
-
# refresh
|
206
|
-
if refresh = meta_tags.delete(:refresh)
|
207
|
-
result << tag(:meta, 'http-equiv' => 'refresh', :content => refresh.to_s) if refresh.present?
|
208
|
-
end
|
209
|
-
|
210
|
-
# alternate
|
211
|
-
if alternate = meta_tags.delete(:alternate)
|
212
|
-
alternate.each do |hreflang, href|
|
213
|
-
result << tag(:link, :rel => 'alternate', :href => href, :hreflang => hreflang) if href.present?
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
# hashes
|
218
|
-
meta_tags.each do |property, data|
|
219
|
-
if data.is_a?(Hash)
|
220
|
-
result.concat process_tree(property, data)
|
221
|
-
meta_tags.delete(property)
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
# canonical, prev, next and author, :publisher
|
226
|
-
[ :canonical, :prev, :next, :author, :publisher ].each do |tag_name|
|
227
|
-
next unless href = meta_tags.delete(tag_name)
|
228
|
-
result << tag(:link, :rel => tag_name, :href => href)
|
229
|
-
end
|
230
|
-
|
231
|
-
# user defined
|
232
|
-
meta_tags.each do |name, data|
|
233
|
-
Array(data).each do |val|
|
234
|
-
result << tag(:meta, :name => name, :content => val)
|
235
|
-
end
|
236
|
-
meta_tags.delete(name)
|
237
|
-
end
|
238
|
-
|
239
|
-
result = result.join("\n")
|
240
|
-
result.html_safe
|
175
|
+
def display_meta_tags(defaults = {})
|
176
|
+
self.meta_tags.with_defaults(defaults) { Renderer.new(meta_tags).render(self) }
|
241
177
|
end
|
242
178
|
|
243
179
|
# Returns full page title as a string without surrounding <title> tag.
|
@@ -247,7 +183,7 @@ module MetaTags
|
|
247
183
|
# so you have to pass default arguments like site title in here. You probably
|
248
184
|
# want to define helper with default options to minimize code duplication.
|
249
185
|
#
|
250
|
-
# @param [Hash]
|
186
|
+
# @param [Hash] defaults list of meta tags.
|
251
187
|
# @option default [String] :site (nil) site title;
|
252
188
|
# @option default [String] :title ("") page title;
|
253
189
|
# @option default [String, Boolean] :prefix (" ") text between site name and separator; when +false+, no prefix will be rendered;
|
@@ -259,95 +195,10 @@ module MetaTags
|
|
259
195
|
# @example
|
260
196
|
# <div data-page-container="true" title="<%= display_title :title => 'My Page', :site => 'PJAX Site' %>">
|
261
197
|
#
|
262
|
-
def display_title(
|
263
|
-
meta_tags
|
264
|
-
build_full_title(meta_tags)
|
198
|
+
def display_title(defaults = {})
|
199
|
+
@meta_tags.full_title(defaults)
|
265
200
|
end
|
266
201
|
|
267
|
-
if
|
268
|
-
safe_helper :display_meta_tags
|
269
|
-
end
|
270
|
-
|
271
|
-
private
|
272
|
-
|
273
|
-
# Recursive function to process all the hashes and arrays on meta tags
|
274
|
-
def process_tree(property, content)
|
275
|
-
result = []
|
276
|
-
if content.is_a?(Hash)
|
277
|
-
content.each do |key, value|
|
278
|
-
result.concat process_tree("#{property}:#{key}", value.is_a?(Symbol) ? meta_tags[value] : value)
|
279
|
-
end
|
280
|
-
else
|
281
|
-
Array(content).each do |c|
|
282
|
-
if c.is_a?(Hash)
|
283
|
-
result.concat process_tree(property, c)
|
284
|
-
else
|
285
|
-
result << tag(:meta, :property => "#{property}", :content => c) unless c.blank?
|
286
|
-
end
|
287
|
-
end
|
288
|
-
end
|
289
|
-
result
|
290
|
-
end
|
291
|
-
|
292
|
-
def normalize_title(title)
|
293
|
-
Array(title).map { |t| h(strip_tags(t)) }
|
294
|
-
end
|
295
|
-
|
296
|
-
def normalize_description(description)
|
297
|
-
return '' if description.blank?
|
298
|
-
truncate(strip_tags(description).gsub(/\s+/, ' '), :length => 200)
|
299
|
-
end
|
300
|
-
|
301
|
-
def normalize_keywords(keywords)
|
302
|
-
return '' if keywords.blank?
|
303
|
-
keywords = keywords.flatten.join(', ') if Array === keywords
|
304
|
-
strip_tags(keywords).downcase
|
305
|
-
end
|
306
|
-
|
307
|
-
def normalize_open_graph(meta_tags)
|
308
|
-
meta_tags = (meta_tags || {}).with_indifferent_access
|
309
|
-
meta_tags[:og] = meta_tags.delete(:open_graph) if meta_tags.key?(:open_graph)
|
310
|
-
meta_tags
|
311
|
-
end
|
312
|
-
|
313
|
-
def build_full_title(meta_tags)
|
314
|
-
# Prefix (leading space)
|
315
|
-
prefix = meta_tags[:prefix] === false ? '' : (meta_tags[:prefix] || ' ')
|
316
|
-
meta_tags.delete(:prefix)
|
317
|
-
|
318
|
-
# Separator
|
319
|
-
separator = meta_tags[:separator] === false ? '' : (meta_tags[:separator] || '|')
|
320
|
-
|
321
|
-
# Suffix (trailing space)
|
322
|
-
suffix = meta_tags[:suffix] === false ? '' : (meta_tags[:suffix] || ' ')
|
323
|
-
meta_tags.delete(:suffix)
|
324
|
-
|
325
|
-
# Special case: if separator is hidden, do not display suffix/prefix
|
326
|
-
if meta_tags[:separator] == false
|
327
|
-
prefix = suffix = ''
|
328
|
-
end
|
329
|
-
meta_tags.delete(:separator)
|
330
|
-
|
331
|
-
# Title
|
332
|
-
title = meta_tags.delete(:title)
|
333
|
-
if meta_tags.delete(:lowercase) === true and !title.blank?
|
334
|
-
title = Array(title).map { |t| t.downcase }
|
335
|
-
end
|
336
|
-
|
337
|
-
# title
|
338
|
-
if title.blank?
|
339
|
-
meta_tags.delete(:reverse)
|
340
|
-
meta_tags.delete(:site)
|
341
|
-
else
|
342
|
-
title = normalize_title(title)
|
343
|
-
title.unshift(h(meta_tags[:site])) unless meta_tags[:site].blank?
|
344
|
-
title.reverse! if meta_tags.delete(:reverse) === true
|
345
|
-
sep = h(prefix) + h(separator) + h(suffix)
|
346
|
-
title = title.join(sep)
|
347
|
-
meta_tags.delete(:site)
|
348
|
-
# We escaped every chunk of the title, so the whole title should be HTML safe
|
349
|
-
title.html_safe
|
350
|
-
end
|
351
|
-
end
|
202
|
+
# safe_helper :display_meta_tags if defined?(:safe_helper)
|
352
203
|
end
|
353
204
|
end
|
@@ -34,7 +34,7 @@ describe MetaTags::ControllerHelper do
|
|
34
34
|
it 'should set meta tags from instance variables' do
|
35
35
|
subject.index
|
36
36
|
subject.rendered.should be_true
|
37
|
-
subject.meta_tags.should eq('title' => 'title', 'keywords' => 'key1, key2, key3', 'description' => 'description')
|
37
|
+
subject.meta_tags.meta_tags.should eq('title' => 'title', 'keywords' => 'key1, key2, key3', 'description' => 'description')
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
data/spec/meta_tags_spec.rb
CHANGED
@@ -500,6 +500,34 @@ describe MetaTags::ViewHelper do
|
|
500
500
|
end
|
501
501
|
end
|
502
502
|
|
503
|
+
context 'displaying Twitter meta tags' do
|
504
|
+
it 'should display meta tags specified with :twitter' do
|
505
|
+
subject.set_meta_tags(:twitter => {
|
506
|
+
:title => 'Twitter Share Title',
|
507
|
+
:card => 'photo',
|
508
|
+
:image => {
|
509
|
+
:_ => 'http://example.com/1.png',
|
510
|
+
:width => 123,
|
511
|
+
:height => 321,
|
512
|
+
}
|
513
|
+
})
|
514
|
+
subject.display_meta_tags(:site => 'someSite').tap do |content|
|
515
|
+
content.should include('<meta content="Twitter Share Title" name="twitter:title" />')
|
516
|
+
content.should include('<meta content="photo" name="twitter:card" />')
|
517
|
+
content.should include('<meta content="http://example.com/1.png" name="twitter:image" />')
|
518
|
+
content.should include('<meta content="123" name="twitter:image:width" />')
|
519
|
+
content.should include('<meta content="321" name="twitter:image:height" />')
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
it "should display mirrored content" do
|
524
|
+
subject.set_meta_tags(:title => 'someTitle')
|
525
|
+
subject.display_meta_tags(:twitter => { :title => :title }).tap do |content|
|
526
|
+
content.should include('<meta content="someTitle" name="twitter:title" />')
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
503
531
|
context 'while handling string meta tag names' do
|
504
532
|
it 'should work with common parameters' do
|
505
533
|
subject.display_meta_tags('site' => 'someSite', 'title' => 'someTitle').should eq('<title>someSite | someTitle</title>')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: meta-tags
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dmytro Shteflyuk
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-04-
|
11
|
+
date: 2014-04-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -100,7 +100,12 @@ files:
|
|
100
100
|
- Rakefile
|
101
101
|
- lib/meta-tags.rb
|
102
102
|
- lib/meta_tags.rb
|
103
|
+
- lib/meta_tags/content_tag.rb
|
103
104
|
- lib/meta_tags/controller_helper.rb
|
105
|
+
- lib/meta_tags/meta_tags_collection.rb
|
106
|
+
- lib/meta_tags/renderer.rb
|
107
|
+
- lib/meta_tags/tag.rb
|
108
|
+
- lib/meta_tags/text_normalizer.rb
|
104
109
|
- lib/meta_tags/version.rb
|
105
110
|
- lib/meta_tags/view_helper.rb
|
106
111
|
- meta-tags.gemspec
|