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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MTVhZmJkYjIxODQ5MTRjZjkxOGY0ODQwYmYwYmVjM2E0ZmYyNWI2NQ==
4
+ NmY1YmU5YTM0ZjQwYzBkODRkZmViODlmMTI1NjljYzdkMzcwMzljYg==
5
5
  data.tar.gz: !binary |-
6
- MjQyMDNhYWNmOGNhMjBkN2RmZWZiODY5NGEzYTQ0NzdmMmFkNjhmMw==
6
+ OGQ1ZjE5YzFjMjcxNzM1OTliZDNhY2MxMDU0M2ExMTkxOTMzZTkxMQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YzAzNDRlZGEwMDIxYWVjOTZhODY2NWZkYWJhMjMyMjNlNjZmYzkzMzc0NmQw
10
- MjAzOTNlYjQ3MjE1ZjY0ZGU0NDA0MDlhZTQ2YTBiZjEzZDdjZjQ0M2Q5YTVk
11
- OTI0NjMwM2MxMjU4Mjc0ZGRiNjI3MmUzMGRmYTdlODY2YmY0YWE=
9
+ MjA0OTRkMGJjMDc1ZGE1NDk5OTExZGRmMDVhZjNiZDFmMTgzYjYxYTYzOWYw
10
+ N2YxYjc4YjI4ZDc3ZjIyYjBmNWJiNjBhNzI2MmY3MmRlMDliMjU2ZTQ5Y2My
11
+ MmM2YTJmZjUzMzFjYWFlMDQxMzJlZmYxMWVkZTdlYjFjMjAwY2U=
12
12
  data.tar.gz: !binary |-
13
- YzA3YzcyN2NjMTg3MWE3YzYzZWVlNTQ5ZDk0M2ZkM2FjNjU5NDc1MTJkNmYy
14
- MzUwNzA0NjUzMTM4YWUzZDJkN2UxNzM3ZDA2MTVlZWMwZmVkY2QxODMxM2I0
15
- NWZmNWM3ZTcwOGVmNDY4NDQ2YzVlNTc3MzA1ZjA3NTU4NGY0M2I=
13
+ M2FmMmJlZGZhNTU0YWIwMTUzYzZhNGY5OWQxNDA4MGYyOTZlOTUzMzM4NTg4
14
+ YzU4MGVhZTNiNzk0NzYwNDQ5NTUwYjM2NTA5MTA3YjQxMzI1MWVhZGUwY2Vl
15
+ NjY5MjVlNTZhYmY1MjNhNjA1MWQ0ZjVkMTU2NjFkZWE2OTI4NWI=
@@ -1,3 +1,13 @@
1
+ ## 2.0.0 (April 15, 2014)
2
+
3
+ Features:
4
+
5
+ - Fully refactored code base.
6
+
7
+ Bugfixes:
8
+
9
+ - Symlink references in nested hashes include use normalized meta tag values.
10
+
1
11
  ## 1.6.0 (April 14, 2014)
2
12
 
3
13
  Features:
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 property="twitter:card" content="summary"/>
244
- # <meta property="twitter:site" content="@username"/>
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']
@@ -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
- require 'meta_tags/view_helper'
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 {InstanceMethods#set_meta_tags} method, that have the same parameters
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
- def self.included(base)
14
- base.send :include, InstanceMethods
15
- base.alias_method_chain :render, :meta_tags
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ alias_method_chain :render, :meta_tags
16
17
  end
17
18
 
18
- module InstanceMethods
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
- meta_tags = {}
23
- meta_tags[:title] = @page_title if @page_title
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
- render_without_meta_tags(*args, &block)
29
- end
26
+ render_without_meta_tags(*args, &block)
27
+ end
28
+ protected :render_with_meta_tags
30
29
 
31
- # Set meta tags for the page.
32
- #
33
- # See <tt>MetaTags.set_meta_tags</tt> for details.
34
- def set_meta_tags(meta_tags)
35
- meta_tags = (meta_tags || {}).with_indifferent_access
36
- meta_tags[:og] = meta_tags.delete(:open_graph) if meta_tags.key?(:open_graph)
37
- self.meta_tags.deep_merge!(meta_tags)
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
- # Get meta tags for the page.
42
- def meta_tags
43
- @meta_tags ||= HashWithIndifferentAccess.new
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
@@ -1,3 +1,4 @@
1
1
  module MetaTags
2
- VERSION = '1.6.0'
2
+ # Gem version.
3
+ VERSION = '2.0.0'
3
4
  end
@@ -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 ||= HashWithIndifferentAccess.new
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.deep_merge! normalize_open_graph(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 divided with configured
39
- # separator value (see {#display_meta_tags}). When nil, current
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] default default meta tag values.
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 name will be lowercase;
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(default = {})
175
- meta_tags = normalize_open_graph(default).deep_merge!(self.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] meta_tags list of meta tags.
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(default = {})
263
- meta_tags = normalize_open_graph(default).deep_merge!(self.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 respond_to? :safe_helper
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
 
@@ -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: 1.6.0
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-14 00:00:00.000000000 Z
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