better_seo 1.0.0.1 → 1.0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +30 -0
- data/.rubocop_todo.yml +360 -0
- data/CHANGELOG.md +44 -0
- data/README.md +7 -25
- data/lib/better_seo/analytics/google_analytics.rb +12 -12
- data/lib/better_seo/analytics/google_tag_manager.rb +7 -7
- data/lib/better_seo/configuration.rb +11 -5
- data/lib/better_seo/dsl/base.rb +1 -1
- data/lib/better_seo/dsl/open_graph.rb +2 -6
- data/lib/better_seo/dsl/twitter_cards.rb +5 -7
- data/lib/better_seo/generators/amp_generator.rb +8 -14
- data/lib/better_seo/generators/breadcrumbs_generator.rb +14 -13
- data/lib/better_seo/generators/canonical_url_manager.rb +8 -14
- data/lib/better_seo/generators/meta_tags_generator.rb +7 -6
- data/lib/better_seo/generators/open_graph_generator.rb +5 -5
- data/lib/better_seo/generators/robots_txt_generator.rb +5 -11
- data/lib/better_seo/generators/twitter_cards_generator.rb +4 -4
- data/lib/better_seo/image/optimizer.rb +2 -0
- data/lib/better_seo/rails/helpers/controller_helpers.rb +3 -1
- data/lib/better_seo/rails/helpers/seo_helper.rb +3 -7
- data/lib/better_seo/rails/helpers/structured_data_helper.rb +2 -2
- data/lib/better_seo/sitemap/generator.rb +1 -1
- data/lib/better_seo/sitemap/sitemap_index.rb +5 -5
- data/lib/better_seo/sitemap/url_entry.rb +9 -11
- data/lib/better_seo/structured_data/base.rb +2 -1
- data/lib/better_seo/structured_data/event.rb +1 -3
- data/lib/better_seo/structured_data/generator.rb +10 -10
- data/lib/better_seo/structured_data/recipe.rb +3 -4
- data/lib/better_seo/validators/seo_recommendations.rb +7 -7
- data/lib/better_seo/validators/seo_validator.rb +33 -23
- data/lib/better_seo/version.rb +1 -1
- data/lib/generators/better_seo/templates/better_seo.rb +2 -2
- metadata +3 -1
|
@@ -37,10 +37,8 @@ module BetterSeo
|
|
|
37
37
|
return get(:image) if value.nil?
|
|
38
38
|
|
|
39
39
|
if value.is_a?(Hash)
|
|
40
|
-
set(:image, value)
|
|
41
|
-
else
|
|
42
|
-
set(:image, value)
|
|
43
40
|
end
|
|
41
|
+
set(:image, value)
|
|
44
42
|
end
|
|
45
43
|
|
|
46
44
|
def image_alt(value = nil)
|
|
@@ -74,10 +72,10 @@ module BetterSeo
|
|
|
74
72
|
else
|
|
75
73
|
# Set for all platforms
|
|
76
74
|
set(:app_name, {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
iphone: value,
|
|
76
|
+
ipad: value,
|
|
77
|
+
googleplay: value
|
|
78
|
+
})
|
|
81
79
|
end
|
|
82
80
|
end
|
|
83
81
|
|
|
@@ -32,22 +32,16 @@ module BetterSeo
|
|
|
32
32
|
def to_meta_tags
|
|
33
33
|
tags = []
|
|
34
34
|
|
|
35
|
-
if @canonical_url
|
|
36
|
-
tags << %(<link rel="canonical" href="#{escape_html(@canonical_url)}">)
|
|
37
|
-
end
|
|
35
|
+
tags << %(<link rel="canonical" href="#{escape_html(@canonical_url)}">) if @canonical_url
|
|
38
36
|
|
|
39
|
-
if @title
|
|
40
|
-
tags << %(<meta property="og:title" content="#{escape_html(@title)}">)
|
|
41
|
-
end
|
|
37
|
+
tags << %(<meta property="og:title" content="#{escape_html(@title)}">) if @title
|
|
42
38
|
|
|
43
39
|
if @description
|
|
44
40
|
tags << %(<meta name="description" content="#{escape_html(@description)}">)
|
|
45
41
|
tags << %(<meta property="og:description" content="#{escape_html(@description)}">)
|
|
46
42
|
end
|
|
47
43
|
|
|
48
|
-
if @image
|
|
49
|
-
tags << %(<meta property="og:image" content="#{escape_html(@image)}">)
|
|
50
|
-
end
|
|
44
|
+
tags << %(<meta property="og:image" content="#{escape_html(@image)}">) if @image
|
|
51
45
|
|
|
52
46
|
tags.join("\n")
|
|
53
47
|
end
|
|
@@ -72,11 +66,11 @@ module BetterSeo
|
|
|
72
66
|
return "" if text.nil?
|
|
73
67
|
|
|
74
68
|
text.to_s
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
69
|
+
.gsub("&", "&")
|
|
70
|
+
.gsub("<", "<")
|
|
71
|
+
.gsub(">", ">")
|
|
72
|
+
.gsub('"', """)
|
|
73
|
+
.gsub("'", "'")
|
|
80
74
|
end
|
|
81
75
|
end
|
|
82
76
|
end
|
|
@@ -46,10 +46,13 @@ module BetterSeo
|
|
|
46
46
|
html << %( <ol class="#{escape_html(list_class)}">)
|
|
47
47
|
|
|
48
48
|
@items.each_with_index do |item, index|
|
|
49
|
+
active_class = item[:url].nil? ? " active" : ""
|
|
50
|
+
aria_current = item[:url].nil? ? ' aria-current="page"' : ""
|
|
51
|
+
|
|
49
52
|
if schema
|
|
50
|
-
html << %( <li class="breadcrumb-item#{
|
|
53
|
+
html << %( <li class="breadcrumb-item#{active_class}" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"#{aria_current}>)
|
|
51
54
|
else
|
|
52
|
-
html << %( <li class="breadcrumb-item#{
|
|
55
|
+
html << %( <li class="breadcrumb-item#{active_class}"#{aria_current}>)
|
|
53
56
|
end
|
|
54
57
|
|
|
55
58
|
if item[:url]
|
|
@@ -61,13 +64,11 @@ module BetterSeo
|
|
|
61
64
|
else
|
|
62
65
|
html << %( <a href="#{escape_html(item[:url])}">#{escape_html(item[:name])}</a>)
|
|
63
66
|
end
|
|
67
|
+
elsif schema
|
|
68
|
+
html << %( <span itemprop="name">#{escape_html(item[:name])}</span>)
|
|
69
|
+
html << %( <meta itemprop="position" content="#{index + 1}" />)
|
|
64
70
|
else
|
|
65
|
-
|
|
66
|
-
html << %( <span itemprop="name">#{escape_html(item[:name])}</span>)
|
|
67
|
-
html << %( <meta itemprop="position" content="#{index + 1}" />)
|
|
68
|
-
else
|
|
69
|
-
html << %( #{escape_html(item[:name])})
|
|
70
|
-
end
|
|
71
|
+
html << %( #{escape_html(item[:name])})
|
|
71
72
|
end
|
|
72
73
|
|
|
73
74
|
html << %( </li>)
|
|
@@ -115,11 +116,11 @@ module BetterSeo
|
|
|
115
116
|
return "" if text.nil?
|
|
116
117
|
|
|
117
118
|
text.to_s
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
.gsub("&", "&")
|
|
120
|
+
.gsub("<", "<")
|
|
121
|
+
.gsub(">", ">")
|
|
122
|
+
.gsub('"', """)
|
|
123
|
+
.gsub("'", "'")
|
|
123
124
|
end
|
|
124
125
|
end
|
|
125
126
|
end
|
|
@@ -42,14 +42,10 @@ module BetterSeo
|
|
|
42
42
|
uri = URI.parse(@url)
|
|
43
43
|
|
|
44
44
|
# Check if it's a relative URL
|
|
45
|
-
unless uri.absolute?
|
|
46
|
-
raise ValidationError, "Canonical URL must be absolute: #{@url}"
|
|
47
|
-
end
|
|
45
|
+
raise ValidationError, "Canonical URL must be absolute: #{@url}" unless uri.absolute?
|
|
48
46
|
|
|
49
47
|
# Check if it's HTTP or HTTPS
|
|
50
|
-
unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
51
|
-
raise ValidationError, "Invalid URL format: #{@url}"
|
|
52
|
-
end
|
|
48
|
+
raise ValidationError, "Invalid URL format: #{@url}" unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
53
49
|
rescue URI::InvalidURIError
|
|
54
50
|
raise ValidationError, "Invalid URL format: #{@url}"
|
|
55
51
|
end
|
|
@@ -81,9 +77,7 @@ module BetterSeo
|
|
|
81
77
|
normalized = uri.to_s
|
|
82
78
|
|
|
83
79
|
# Remove trailing slash (except for root URL)
|
|
84
|
-
if normalized.end_with?("/") && normalized.count("/") > 3
|
|
85
|
-
normalized = normalized[0...-1]
|
|
86
|
-
end
|
|
80
|
+
normalized = normalized[0...-1] if normalized.end_with?("/") && normalized.count("/") > 3
|
|
87
81
|
|
|
88
82
|
# Lowercase if configured
|
|
89
83
|
normalized = normalized.downcase if @lowercase
|
|
@@ -95,11 +89,11 @@ module BetterSeo
|
|
|
95
89
|
return "" if text.nil?
|
|
96
90
|
|
|
97
91
|
text.to_s
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
.gsub("&", "&")
|
|
93
|
+
.gsub("<", "<")
|
|
94
|
+
.gsub(">", ">")
|
|
95
|
+
.gsub('"', """)
|
|
96
|
+
.gsub("'", "'")
|
|
103
97
|
end
|
|
104
98
|
end
|
|
105
99
|
end
|
|
@@ -47,7 +47,7 @@ module BetterSeo
|
|
|
47
47
|
|
|
48
48
|
def keywords_tag
|
|
49
49
|
keywords = @config[:keywords]
|
|
50
|
-
return nil unless keywords
|
|
50
|
+
return nil unless keywords&.any?
|
|
51
51
|
|
|
52
52
|
keywords_str = Array(keywords).join(", ")
|
|
53
53
|
%(<meta name="keywords" content="#{escape(keywords_str)}">)
|
|
@@ -79,7 +79,8 @@ module BetterSeo
|
|
|
79
79
|
|
|
80
80
|
# Additional directives
|
|
81
81
|
robots.each do |key, value|
|
|
82
|
-
next if [
|
|
82
|
+
next if %i[index follow].include?(key)
|
|
83
|
+
|
|
83
84
|
directives << key.to_s if value
|
|
84
85
|
end
|
|
85
86
|
|
|
@@ -90,10 +91,10 @@ module BetterSeo
|
|
|
90
91
|
|
|
91
92
|
def escape(text)
|
|
92
93
|
text.to_s
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
.gsub("&", "&")
|
|
95
|
+
.gsub('"', """)
|
|
96
|
+
.gsub("<", "<")
|
|
97
|
+
.gsub(">", ">")
|
|
97
98
|
end
|
|
98
99
|
end
|
|
99
100
|
end
|
|
@@ -52,7 +52,7 @@ module BetterSeo
|
|
|
52
52
|
|
|
53
53
|
def locale_alternate_tags
|
|
54
54
|
alternates = @config[:locale_alternate]
|
|
55
|
-
return [] unless alternates
|
|
55
|
+
return [] unless alternates&.any?
|
|
56
56
|
|
|
57
57
|
Array(alternates).map do |locale|
|
|
58
58
|
meta_tag("og:locale:alternate", locale)
|
|
@@ -100,10 +100,10 @@ module BetterSeo
|
|
|
100
100
|
|
|
101
101
|
def escape(text)
|
|
102
102
|
text.to_s
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
.gsub("&", "&")
|
|
104
|
+
.gsub('"', """)
|
|
105
|
+
.gsub("<", "<")
|
|
106
|
+
.gsub(">", ">")
|
|
107
107
|
end
|
|
108
108
|
end
|
|
109
109
|
end
|
|
@@ -58,23 +58,17 @@ module BetterSeo
|
|
|
58
58
|
lines << "User-agent: #{rule[:user_agent]}"
|
|
59
59
|
|
|
60
60
|
# Allow directives
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
lines << "Allow: #{path}"
|
|
64
|
-
end
|
|
61
|
+
rule[:allow]&.each do |path|
|
|
62
|
+
lines << "Allow: #{path}"
|
|
65
63
|
end
|
|
66
64
|
|
|
67
65
|
# Disallow directives
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
lines << "Disallow: #{path}"
|
|
71
|
-
end
|
|
66
|
+
rule[:disallow]&.each do |path|
|
|
67
|
+
lines << "Disallow: #{path}"
|
|
72
68
|
end
|
|
73
69
|
|
|
74
70
|
# Crawl delay
|
|
75
|
-
if rule[:crawl_delay]
|
|
76
|
-
lines << "Crawl-delay: #{rule[:crawl_delay]}"
|
|
77
|
-
end
|
|
71
|
+
lines << "Crawl-delay: #{rule[:crawl_delay]}" if rule[:crawl_delay]
|
|
78
72
|
|
|
79
73
|
# Add blank line between user agent sections (except last)
|
|
80
74
|
lines << "" if index < @rules.size - 1
|
|
@@ -92,10 +92,10 @@ module BetterSeo
|
|
|
92
92
|
|
|
93
93
|
def escape(text)
|
|
94
94
|
text.to_s
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
.gsub("&", "&")
|
|
96
|
+
.gsub('"', """)
|
|
97
|
+
.gsub("<", "<")
|
|
98
|
+
.gsub(">", ">")
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
end
|
|
@@ -24,6 +24,7 @@ module BetterSeo
|
|
|
24
24
|
unless SUPPORTED_FORMATS.include?(ext)
|
|
25
25
|
raise ImageError, "Unsupported format: #{ext}. Supported: #{SUPPORTED_FORMATS.join(", ")}"
|
|
26
26
|
end
|
|
27
|
+
|
|
27
28
|
true
|
|
28
29
|
end
|
|
29
30
|
|
|
@@ -136,6 +137,7 @@ module BetterSeo
|
|
|
136
137
|
|
|
137
138
|
def validate_source!(source)
|
|
138
139
|
raise ImageError, "Source file not found: #{source}" unless File.exist?(source)
|
|
140
|
+
|
|
139
141
|
validate_format!(source)
|
|
140
142
|
end
|
|
141
143
|
end
|
|
@@ -62,9 +62,11 @@ module BetterSeo
|
|
|
62
62
|
def set_page_description(description, max_length: nil)
|
|
63
63
|
final_description = description
|
|
64
64
|
|
|
65
|
+
# rubocop:disable Style/IfUnlessModifier
|
|
65
66
|
if max_length && description.length > max_length
|
|
66
|
-
final_description = description[0...max_length - 3]
|
|
67
|
+
final_description = "#{description[0...(max_length - 3)]}..."
|
|
67
68
|
end
|
|
69
|
+
# rubocop:enable Style/IfUnlessModifier
|
|
68
70
|
|
|
69
71
|
merge_seo_data(:meta, { description: final_description })
|
|
70
72
|
end
|
|
@@ -41,7 +41,7 @@ module BetterSeo
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# Generate all SEO tags (meta + og + twitter) from configuration or block
|
|
44
|
-
def seo_tags(config = nil
|
|
44
|
+
def seo_tags(config = nil)
|
|
45
45
|
if block_given?
|
|
46
46
|
context = SeoTagsContext.new
|
|
47
47
|
yield(context)
|
|
@@ -55,9 +55,7 @@ module BetterSeo
|
|
|
55
55
|
|
|
56
56
|
tags = []
|
|
57
57
|
|
|
58
|
-
if merged_config[:meta]
|
|
59
|
-
tags << seo_meta_tags(merged_config[:meta])
|
|
60
|
-
end
|
|
58
|
+
tags << seo_meta_tags(merged_config[:meta]) if merged_config[:meta]
|
|
61
59
|
|
|
62
60
|
if merged_config[:og] && BetterSeo.configuration.open_graph_enabled?
|
|
63
61
|
tags << seo_open_graph_tags(merged_config[:og])
|
|
@@ -81,9 +79,7 @@ module BetterSeo
|
|
|
81
79
|
meta = {}
|
|
82
80
|
|
|
83
81
|
# Start with controller data
|
|
84
|
-
if controller_data[:meta]
|
|
85
|
-
meta = controller_data[:meta].dup
|
|
86
|
-
end
|
|
82
|
+
meta = controller_data[:meta].dup if controller_data[:meta]
|
|
87
83
|
|
|
88
84
|
# Use default title if not set
|
|
89
85
|
meta[:title] ||= config.meta_tags.default_title
|
|
@@ -27,7 +27,7 @@ module BetterSeo
|
|
|
27
27
|
raw(sd_object.to_script_tag)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def structured_data_tags(objects = nil
|
|
30
|
+
def structured_data_tags(objects = nil)
|
|
31
31
|
array = block_given? ? yield : objects
|
|
32
32
|
return "" if array.nil? || array.empty?
|
|
33
33
|
|
|
@@ -109,7 +109,7 @@ module BetterSeo
|
|
|
109
109
|
|
|
110
110
|
private
|
|
111
111
|
|
|
112
|
-
def create_structured_data(type, properties
|
|
112
|
+
def create_structured_data(type, properties)
|
|
113
113
|
klass = TYPE_MAPPING[type]
|
|
114
114
|
raise ArgumentError, "Unknown structured data type: #{type}" unless klass
|
|
115
115
|
|
|
@@ -62,11 +62,11 @@ module BetterSeo
|
|
|
62
62
|
|
|
63
63
|
def escape_xml(text)
|
|
64
64
|
text.to_s
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
.gsub("&", "&")
|
|
66
|
+
.gsub("<", "<")
|
|
67
|
+
.gsub(">", ">")
|
|
68
|
+
.gsub('"', """)
|
|
69
|
+
.gsub("'", "'")
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
end
|
|
@@ -28,13 +28,13 @@ module BetterSeo
|
|
|
28
28
|
unless VALID_CHANGEFREQ.include?(value)
|
|
29
29
|
raise ValidationError, "Invalid changefreq: #{value}. Must be one of: #{VALID_CHANGEFREQ.join(", ")}"
|
|
30
30
|
end
|
|
31
|
+
|
|
31
32
|
@changefreq = value
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
def priority=(value)
|
|
35
|
-
unless value.between?(0.0, 1.0)
|
|
36
|
-
|
|
37
|
-
end
|
|
36
|
+
raise ValidationError, "Priority must be between 0.0 and 1.0" unless value.between?(0.0, 1.0)
|
|
37
|
+
|
|
38
38
|
@priority = value
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -119,9 +119,7 @@ module BetterSeo
|
|
|
119
119
|
|
|
120
120
|
begin
|
|
121
121
|
uri = URI.parse(@loc)
|
|
122
|
-
unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
123
|
-
raise ValidationError, "Invalid URL format: #{@loc}"
|
|
124
|
-
end
|
|
122
|
+
raise ValidationError, "Invalid URL format: #{@loc}" unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
125
123
|
rescue URI::InvalidURIError
|
|
126
124
|
raise ValidationError, "Invalid URL format: #{@loc}"
|
|
127
125
|
end
|
|
@@ -146,11 +144,11 @@ module BetterSeo
|
|
|
146
144
|
|
|
147
145
|
def escape_xml(text)
|
|
148
146
|
text.to_s
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
147
|
+
.gsub("&", "&")
|
|
148
|
+
.gsub("<", "<")
|
|
149
|
+
.gsub(">", ">")
|
|
150
|
+
.gsub('"', """)
|
|
151
|
+
.gsub("'", "'")
|
|
154
152
|
end
|
|
155
153
|
end
|
|
156
154
|
end
|
|
@@ -35,7 +35,7 @@ module BetterSeo
|
|
|
35
35
|
hash
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
def to_json(*
|
|
38
|
+
def to_json(*_args)
|
|
39
39
|
JSON.pretty_generate(to_h)
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -53,6 +53,7 @@ module BetterSeo
|
|
|
53
53
|
|
|
54
54
|
def validate!
|
|
55
55
|
raise ValidationError, "@type is required for structured data" unless valid?
|
|
56
|
+
|
|
56
57
|
true
|
|
57
58
|
end
|
|
58
59
|
|
|
@@ -82,9 +82,7 @@ module BetterSeo
|
|
|
82
82
|
location_hash["name"] = name if name
|
|
83
83
|
location_hash["url"] = url if url
|
|
84
84
|
|
|
85
|
-
if address
|
|
86
|
-
location_hash["address"] = build_address(address)
|
|
87
|
-
end
|
|
85
|
+
location_hash["address"] = build_address(address) if address
|
|
88
86
|
|
|
89
87
|
other_properties.each do |key, value|
|
|
90
88
|
location_hash[key.to_s] = value
|
|
@@ -10,61 +10,61 @@ module BetterSeo
|
|
|
10
10
|
structured_data_array.map(&:to_script_tag).join("\n\n")
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def organization(**properties
|
|
13
|
+
def organization(**properties)
|
|
14
14
|
org = Organization.new(**properties)
|
|
15
15
|
yield(org) if block_given?
|
|
16
16
|
org
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def article(**properties
|
|
19
|
+
def article(**properties)
|
|
20
20
|
article = Article.new(**properties)
|
|
21
21
|
yield(article) if block_given?
|
|
22
22
|
article
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
def person(**properties
|
|
25
|
+
def person(**properties)
|
|
26
26
|
person = Person.new(**properties)
|
|
27
27
|
yield(person) if block_given?
|
|
28
28
|
person
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def product(**properties
|
|
31
|
+
def product(**properties)
|
|
32
32
|
product = Product.new(**properties)
|
|
33
33
|
yield(product) if block_given?
|
|
34
34
|
product
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
def breadcrumb_list(**properties
|
|
37
|
+
def breadcrumb_list(**properties)
|
|
38
38
|
breadcrumb = BreadcrumbList.new(**properties)
|
|
39
39
|
yield(breadcrumb) if block_given?
|
|
40
40
|
breadcrumb
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
def local_business(**properties
|
|
43
|
+
def local_business(**properties)
|
|
44
44
|
business = LocalBusiness.new(**properties)
|
|
45
45
|
yield(business) if block_given?
|
|
46
46
|
business
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
def event(**properties
|
|
49
|
+
def event(**properties)
|
|
50
50
|
event = Event.new(**properties)
|
|
51
51
|
yield(event) if block_given?
|
|
52
52
|
event
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
def faq_page(**properties
|
|
55
|
+
def faq_page(**properties)
|
|
56
56
|
faq = FAQPage.new(**properties)
|
|
57
57
|
yield(faq) if block_given?
|
|
58
58
|
faq
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
def how_to(**properties
|
|
61
|
+
def how_to(**properties)
|
|
62
62
|
how_to = HowTo.new(**properties)
|
|
63
63
|
yield(how_to) if block_given?
|
|
64
64
|
how_to
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
def recipe(**properties
|
|
67
|
+
def recipe(**properties)
|
|
68
68
|
recipe = Recipe.new(**properties)
|
|
69
69
|
yield(recipe) if block_given?
|
|
70
70
|
recipe
|
|
@@ -79,7 +79,8 @@ module BetterSeo
|
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
# Nutrition information
|
|
82
|
-
def nutrition(calories: nil, fat_content: nil, carbohydrate_content: nil, protein_content: nil,
|
|
82
|
+
def nutrition(calories: nil, fat_content: nil, carbohydrate_content: nil, protein_content: nil,
|
|
83
|
+
sugar_content: nil, **other)
|
|
83
84
|
nutrition_hash = {
|
|
84
85
|
"@type" => "NutritionInformation"
|
|
85
86
|
}
|
|
@@ -114,9 +115,7 @@ module BetterSeo
|
|
|
114
115
|
def to_h
|
|
115
116
|
hash = super
|
|
116
117
|
|
|
117
|
-
if @ingredients.any?
|
|
118
|
-
hash["recipeIngredient"] = @ingredients
|
|
119
|
-
end
|
|
118
|
+
hash["recipeIngredient"] = @ingredients if @ingredients.any?
|
|
120
119
|
|
|
121
120
|
if @instructions.any?
|
|
122
121
|
hash["recipeInstructions"] = @instructions.map.with_index(1) do |instruction, index|
|
|
@@ -22,7 +22,7 @@ module BetterSeo
|
|
|
22
22
|
recommendations = []
|
|
23
23
|
length = title_result[:length]
|
|
24
24
|
|
|
25
|
-
if length
|
|
25
|
+
if length.zero?
|
|
26
26
|
recommendations << {
|
|
27
27
|
category: "Title",
|
|
28
28
|
priority: :high,
|
|
@@ -56,7 +56,7 @@ module BetterSeo
|
|
|
56
56
|
recommendations = []
|
|
57
57
|
length = desc_result[:length]
|
|
58
58
|
|
|
59
|
-
if length
|
|
59
|
+
if length.zero?
|
|
60
60
|
recommendations << {
|
|
61
61
|
category: "Meta Description",
|
|
62
62
|
priority: :high,
|
|
@@ -90,7 +90,7 @@ module BetterSeo
|
|
|
90
90
|
recommendations = []
|
|
91
91
|
h1_count = headings_result[:h1_count]
|
|
92
92
|
|
|
93
|
-
if h1_count
|
|
93
|
+
if h1_count.zero?
|
|
94
94
|
recommendations << {
|
|
95
95
|
category: "Headings",
|
|
96
96
|
priority: :high,
|
|
@@ -106,7 +106,7 @@ module BetterSeo
|
|
|
106
106
|
}
|
|
107
107
|
end
|
|
108
108
|
|
|
109
|
-
if headings_result[:total_headings]
|
|
109
|
+
if headings_result[:total_headings].zero?
|
|
110
110
|
recommendations << {
|
|
111
111
|
category: "Headings",
|
|
112
112
|
priority: :medium,
|
|
@@ -123,13 +123,13 @@ module BetterSeo
|
|
|
123
123
|
|
|
124
124
|
recommendations = []
|
|
125
125
|
|
|
126
|
-
if images_result[:images_without_alt]
|
|
126
|
+
if images_result[:images_without_alt].positive?
|
|
127
127
|
count = images_result[:images_without_alt]
|
|
128
128
|
recommendations << {
|
|
129
129
|
category: "Images",
|
|
130
130
|
priority: :medium,
|
|
131
131
|
action: "Add alt text to images",
|
|
132
|
-
details: "#{count} image#{count > 1
|
|
132
|
+
details: "#{count} image#{"s" if count > 1} missing alt text. Alt text improves accessibility and SEO."
|
|
133
133
|
}
|
|
134
134
|
end
|
|
135
135
|
|
|
@@ -145,7 +145,7 @@ module BetterSeo
|
|
|
145
145
|
# Group by priority
|
|
146
146
|
grouped = recommendations.group_by { |r| r[:priority] }
|
|
147
147
|
|
|
148
|
-
[
|
|
148
|
+
%i[high medium low].each do |priority|
|
|
149
149
|
next unless grouped[priority]
|
|
150
150
|
|
|
151
151
|
output << "## #{priority.to_s.capitalize} Priority\n"
|