perron 0.7.0 → 0.9.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ab1fe5550ba3bfd474381303929b49e9dac552d2d086bcbeb79c0760ad59979
4
- data.tar.gz: 4b64af0d6128def66419351d50f74dffc29e8c74b1cf6f00ec004dc122f426cb
3
+ metadata.gz: 2f225e6b75be66a9fd394e0c87b3c63c878042664e3acfeaa7ff68cc449af1b1
4
+ data.tar.gz: a963c546505ffa227310b7e0d8cba7a40446d0a70b86b8491f7baa761bf6e0cb
5
5
  SHA512:
6
- metadata.gz: a3490ff02b1c8612e99f6403d0db98008b88461dd73b430b3470623a33bd4bd5bdc2945ff61a7bae821e1ff1bbcd3ba9f707be86c28724807300cdf75f476493
7
- data.tar.gz: 505b439b20f2dbc5d2310aa56d8b8bcd99b793ddf83395f5652be1814e598c26d3f2154cde99531ad79d621ef7377cb12ddc076677e233a54c96faa875ff4cbf
6
+ metadata.gz: 3f9926aba1d4c88204131fc61564173334a059bcc832c7457f0c31a276b1e05d1466c196afc8a25f54091402436618bee7ae97c6397fdb0660a1da50dbb80231
7
+ data.tar.gz: 54f74446e210fb7ac299b0dc5496bcce18676128fdf9af994403513f8f4ddff37aee8933e58cc5d4e3cbf7316667b0b19860863ddaec6da78ca79c9ac81d3fd6
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- perron (0.7.0)
4
+ perron (0.9.0)
5
5
  csv
6
6
  json
7
7
  psych
data/README.md CHANGED
@@ -17,11 +17,17 @@ A Rails-based static site generator.
17
17
 
18
18
  ### Installation
19
19
 
20
- Start by generating the configuration file:
20
+ Start by adding Perron:
21
+ ```bash
22
+ bundle add perron
23
+ ```
24
+
25
+ Then generate the initializer:
21
26
  ```bash
22
27
  rails generate perron:install
23
28
  ```
24
29
 
30
+
25
31
  This creates an initializer:
26
32
  ```ruby
27
33
  Perron.configure do |config|
@@ -85,6 +91,46 @@ bundle add {commonmarker,kramdown,redcarpet}
85
91
  ```
86
92
 
87
93
 
94
+ ## HTML Transformations
95
+
96
+ Perron can post-process the HTML generated from your Markdown content.
97
+
98
+
99
+ ### Usage
100
+
101
+ Apply transformations by passing an array of processor names or classes to the `markdownify` helper via the `process` option.
102
+ ```erb
103
+ <%= markdownify @resource.content, process: %w[target_blank lazy_load_images] %>
104
+ ```
105
+
106
+
107
+ ### Available Processors
108
+
109
+ The following processors are built-in and can be activated by passing their string name:
110
+
111
+ - `target_blank`: Adds `target="_blank"` to all external links;
112
+ - `lazy_load_images`: Adds `loading="lazy"` to all `<img>` tags.
113
+
114
+
115
+ ### Creating Your Own
116
+
117
+ You can create your own processor by defining a class that inherits from `Perron::HtmlProcessor::Base` and implements a `process` method.
118
+ Then, pass the class constant directly in the `process` array.
119
+
120
+ ```ruby
121
+ # app/processors/add_nofollow_processor.rb
122
+ class AddNofollowProcessor < Perron::HtmlProcessor::Base
123
+ def process
124
+ @html.css("a[target=_blank]").each { it["rel"] = "nofollow" }
125
+ end
126
+ end
127
+ ```
128
+
129
+ ```erb
130
+ <%= markdownify @resource.content, process: ["target_blank", AddNofollowProcessor] %>
131
+ ```
132
+
133
+
88
134
  ## Data Files
89
135
 
90
136
  Perron can consume structured data from YML, JSON, or CSV files, making them available within your templates.
@@ -92,9 +138,9 @@ This is useful for populating features, team members, or any other repeated data
92
138
 
93
139
  ### Usage
94
140
 
95
- To use a data file, instantiate `Perron::Data` with the basename of the file and iterate over the result.
141
+ To use a data file, instantiate `Perron::Site.data` with the basename of the file and iterate over the result.
96
142
  ```erb
97
- <% Perron::Data.new("features").each do |feature| %>
143
+ <% Perron::Site.data.features.each do |feature| %>
98
144
  <h4><%= feature.name %></h4>
99
145
  <p><%= feature.description %></p>
100
146
  <% end %>
@@ -103,7 +149,7 @@ To use a data file, instantiate `Perron::Data` with the basename of the file and
103
149
  ### File Location and Formats
104
150
 
105
151
  By default, Perron looks up `app/content/data/` for files with a `.yml`, `.json`, or `.csv` extension.
106
- For a `new("features")` call, it would find `features.yml`, `features.json`, or `features.csv`. You can also provide a full, absolute path to any data file.
152
+ For a `features` call, it would find `features.yml`, `features.json`, or `features.csv`. You can also provide a path to any data file, via `Perron::Data.new("path/to/data.json")`.
107
153
 
108
154
  ### Accessing Data
109
155
 
@@ -114,11 +160,55 @@ feature[:name]
114
160
  ```
115
161
 
116
162
 
163
+ ## Feeds
164
+
165
+ The `feeds` helper automatically generates HTML `<link>` tags for your site's RSS and JSON feeds.
166
+
167
+
168
+ ### Usage
169
+
170
+ In your layout (e.g., `app/views/layouts/application.html.erb`), add the helper to the `<head>` section:
171
+ ```erb
172
+ <head>
173
+
174
+ <%= feeds %>
175
+
176
+ </head>
177
+ ```
178
+
179
+ To render feeds for specific collections, such as `posts`:
180
+ ```erb
181
+ <%= feeds only: %w[posts] %>
182
+ ```
183
+
184
+ Similarly, you can exclude collections:
185
+ ```erb
186
+ <%= feeds except: %w[pages] %>
187
+ ```
188
+
189
+
190
+ ### Configuration
191
+
192
+ Feeds are configured within the `Resource` class corresponding to a collection:
193
+ ```ruby
194
+ # app/models/content/post.rb
195
+ class Content::Post < Perron::Resource
196
+ configure do |config|
197
+ config.feeds.rss.enabled = true
198
+ # config.feeds.rss.path = "path-to-feed.xml"
199
+ # config.feeds.rss.max_items = 25
200
+ config.feeds.json.enabled = true
201
+ # config.feeds.json.max_items = 15
202
+ # config.feeds.json.path = "path-to-feed.json"
203
+ end
204
+ end
205
+ ```
206
+
207
+
117
208
  ## Metatags
118
209
 
119
210
  The `meta_tags` helper automatically generates SEO and social sharing meta tags for your pages.
120
211
 
121
-
122
212
  ### Usage
123
213
 
124
214
  In your layout (e.g., `app/views/layouts/application.html.erb`), add the helper to the `<head>` section:
@@ -174,7 +264,19 @@ author: Kendall
174
264
  Your content here…
175
265
  ```
176
266
 
177
- #### 3. Default Values
267
+ #### 3. Collection configuration
268
+
269
+ Set site-wide defaults in the initializer:
270
+ ```ruby
271
+ class Content::Post < Perron::Resource
272
+ # …
273
+
274
+ config.metadata.description = "AI-powered tool to keep your knowledge base articles images/screenshots and content up-to-date"
275
+ config.metadata.author = "Rails Designer"
276
+ end
277
+ ```
278
+
279
+ #### 4. Default Values
178
280
 
179
281
  Set site-wide defaults in the initializer:
180
282
  ```ruby
@@ -187,6 +289,61 @@ end
187
289
  ```
188
290
 
189
291
 
292
+ ## Related Resources
293
+
294
+ The `related_resources` method allows to find and display a list of similar resources
295
+ from the same collection. Similarity is calculated using the **TF-IDF** algorithm on the content of each resource.
296
+
297
+
298
+ ### Basic Usage
299
+
300
+ To get a list of the 5 most similar resources, call the method on any resource instance.
301
+ ```ruby
302
+ # app/views/content/posts/show.html.erb
303
+ @resource.related_resources
304
+
305
+ # Just the 3 most similar resources
306
+ @resource.related_resources(limit: 3)
307
+ ```
308
+
309
+
310
+ ## XML Sitemap
311
+
312
+ A sitemap is an XML file that lists all the pages of a website to help search engines discover and index content more efficiently, typically containing URLs, last modification dates, change frequency, and priority values.
313
+
314
+ Enable it with the following line in the Perron configuration:
315
+ ```ruby
316
+ Perron.configure do |config|
317
+ # …
318
+ config.sitemap.enabled = true
319
+ # config.sitemap.priority = 0.8
320
+ # config.sitemap.change_frequency = :monthly
321
+ # …
322
+ end
323
+ ```
324
+
325
+ Values can be overridden per collection…
326
+ ```ruby
327
+ # app/models/content/post.rb
328
+ class Content::Post < Perron::Resource
329
+ configure do |config|
330
+ config.sitemap.enabled = false
331
+ config.sitemap.priority = 0.5
332
+ config.sitemap.change_frequency = :weekly
333
+ end
334
+ end
335
+ ```
336
+
337
+ …or on a resource basis:
338
+ ```ruby
339
+ # app/content/posts/my-first-post.md
340
+ ---
341
+ sitemap_priority: 0.25
342
+ sitemap_change_frequency: :daily
343
+ ---
344
+ ```
345
+
346
+
190
347
  ## Building Your Static Site
191
348
 
192
349
  When in `standalone` mode and you're ready to generate your static site, run:
@@ -203,9 +360,11 @@ Sites that use Perron.
203
360
 
204
361
  ### Standalone (as a SSG)
205
362
  - [AppRefresher](https://apprefresher.com)
363
+ - [Helptail](https://helptail.com)
206
364
 
207
365
  ### Integrated (part of a Rails app)
208
- - [Rails Designers (private community for Rails UI engineers](https://railsdesigners.com)
366
+ - [Rails Designers (private community for Rails UI engineers)](https://railsdesigners.com)
367
+
209
368
 
210
369
  ## Contributing
211
370
 
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FeedsHelper
4
+ def feeds(options = {}) = Perron::Feeds.new.render(options)
5
+ end
@@ -1,17 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MetaTagsHelper
4
- def meta_tags(options = {})
5
- Perron::Metatags.new(source).render(options)
6
- end
4
+ def meta_tags(options = {}) = Perron::Metatags.new(resource).render(options)
7
5
 
8
6
  private
9
7
 
10
- Source = Data.define(:path, :metadata, :published_at)
8
+ Resource = Data.define(:path, :metadata, :published_at)
11
9
 
12
- def source
10
+ def resource
13
11
  return Source.new(request.path, @metadata, nil) if @metadata.present?
14
12
 
15
- @resource || Source.new(request.path, {}, nil)
13
+ @resource || Resource.new(request.path, {}, nil)
16
14
  end
17
15
  end
@@ -4,6 +4,10 @@ require "perron/markdown"
4
4
 
5
5
  module Perron
6
6
  module MarkdownHelper
7
- def markdownify(content = nil, &block) = Perron::Markdown.render(content || capture(&block).strip_heredoc)
7
+ def markdownify(content = nil, options = {}, &block)
8
+ processors = options.fetch(:process, [])
9
+
10
+ Perron::Markdown.render(content || capture(&block).strip_heredoc, processors: processors)
11
+ end
8
12
  end
9
13
  end
@@ -7,7 +7,7 @@ module Perron
7
7
  end
8
8
 
9
9
  def create_data_directory
10
- data_directory = Rails.root.join("app", "views", "content", "data")
10
+ data_directory = Rails.root.join("app", "content", "data")
11
11
  empty_directory data_directory
12
12
 
13
13
  template "README.md.tt", File.join(data_directory, "README.md")
@@ -6,19 +6,29 @@ This is useful for populating features, team members, or any other repeated data
6
6
 
7
7
  ## Usage
8
8
 
9
- To use a data file, instantiate `Perron::Data` with the basename of the file and iterate over the result.
9
+ To use a data file, you can access it through the `Perron::Site.data` object followed by the basename of the file:
10
10
  ```erb
11
- <% Perron::Data.new("features").each do |feature| %>
12
- <h4><%= feature.name %></h4>
11
+ <%% Perron::Site.data.features.each do |feature| %>
12
+ <h4><%%= feature.name %></h4>
13
13
 
14
- <p><%= feature.description %></p>
15
- <% end %>
14
+ <p><%%= feature.description %></p>
15
+ <%% end %>
16
16
  ```
17
17
 
18
+ This is a convenient shorthand for `Perron::Data.new("features")`, which can also be used directly:
19
+ ```ruby
20
+ 
<%% Perron::Data.new("features").each do |feature| %>
21
+ <h4><%%= feature.name %></h4>
22
+
23
+ <p><%%= feature.description %></p>
24
+ <%% end %>
25
+ ```
26
+
27
+
18
28
  ## File Location and Formats
19
29
 
20
30
  By default, Perron looks up `app/content/data/` for files with a `.yml`, `.json`, or `.csv` extension.
21
- For a `new("features")` call, it would find `features.yml`, `features.json`, or `features.csv`. You can also provide a full, absolute path to any data file.
31
+ For a `new("features")` call, it would find `features.yml`, `features.json`, or `features.csv`. You can also provide a full, absolute path to any data file, like `Perron::Data.new("path-to-some-data-file")`.
22
32
 
23
33
 
24
34
  ## Accessing Data
@@ -9,10 +9,6 @@ module Perron
9
9
  yield(configuration)
10
10
  end
11
11
 
12
- def self.reset_configuration!
13
- @configuration = Configuration.new
14
- end
15
-
16
12
  class Configuration
17
13
  def initialize
18
14
  @config = ActiveSupport::OrderedOptions.new
@@ -23,6 +19,7 @@ module Perron
23
19
  @config.include_root = false
24
20
 
25
21
  @config.site_name = nil
22
+ @config.site_description = nil
26
23
 
27
24
  @config.allowed_extensions = [".erb", ".md"]
28
25
  @config.exclude_from_public = %w[assets storage]
@@ -34,11 +31,16 @@ module Perron
34
31
  trailing_slash: ENV.fetch("PERRON_TRAILING_SLASH", "true") == "true"
35
32
  }
36
33
 
34
+ @config.sitemap = ActiveSupport::OrderedOptions.new
35
+ @config.sitemap.enabled = false
36
+ @config.sitemap.priority = 0.5
37
+ @config.sitemap.change_frequency = :monthly
38
+
37
39
  @config.metadata = ActiveSupport::OrderedOptions.new
38
40
  @config.metadata.title_separator = " — "
39
41
  end
40
42
 
41
- def input = "app/content"
43
+ def input = Rails.root.join("app", "content")
42
44
 
43
45
  def output
44
46
  mode.integrated? ? "public" : @config.output
@@ -48,6 +50,13 @@ module Perron
48
50
 
49
51
  def exclude_root? = !@config.include_root
50
52
 
53
+ def url
54
+ options = Perron.configuration.default_url_options
55
+ path = options[:trailing_slash] ? "/" : ""
56
+
57
+ URI.join("#{options[:protocol]}://#{options[:host]}", path).to_s
58
+ end
59
+
51
60
  def method_missing(method_name, ...)
52
61
  if @config.respond_to?(method_name)
53
62
  @config.send(method_name, ...)
data/lib/perron/errors.rb CHANGED
@@ -9,5 +9,9 @@ module Perron
9
9
  class UnsupportedDataFormatError < StandardError; end
10
10
 
11
11
  class DataParseError < StandardError; end
12
+
13
+ class ProcessorNotFoundError < StandardError; end
14
+
15
+ class InvalidProcessorError < StandardError; end
12
16
  end
13
17
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Perron
4
+ class Feeds
5
+ include ActionView::Helpers::TagHelper
6
+
7
+ def render(options = {})
8
+ html_tags = []
9
+
10
+ Rails.application.routes.url_helpers.with_options(Perron.configuration.default_url_options) do |url|
11
+ Perron::Site.collections.each do |collection|
12
+ collection_name = collection.name.to_s
13
+
14
+ next if options[:only]&.map(&:to_s)&.exclude?(collection_name)
15
+ next if options[:except]&.map(&:to_s)&.include?(collection_name)
16
+
17
+ collection.configuration.feeds.each do |type, feed|
18
+ next unless feed.enabled && feed.path && MIME_TYPES.key?(type)
19
+
20
+ absolute_url = URI.join(url.root_url, feed.path).to_s
21
+ title = "#{collection.name.humanize} #{type.to_s.upcase} Feed"
22
+
23
+ html_tags << tag(:link, rel: "alternate", type: MIME_TYPES[type], title: title, href: absolute_url)
24
+ end
25
+ end
26
+ end
27
+
28
+ safe_join(html_tags, "\n")
29
+ end
30
+
31
+ private
32
+
33
+ MIME_TYPES = {
34
+ rss: "application/rss+xml",
35
+ json: "application/json"
36
+ }
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Perron
4
+ class HtmlProcessor
5
+ class Base
6
+ def initialize(html)
7
+ @html = html
8
+ end
9
+
10
+ def process
11
+ raise NotImplementedError
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Perron
4
+ class HtmlProcessor
5
+ class LazyLoadImages < HtmlProcessor::Base
6
+ def process
7
+ @html.css("img").each do |image|
8
+ image["loading"] = "lazy"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,21 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "perron/html_processor/base"
4
+
3
5
  module Perron
4
6
  class HtmlProcessor
5
- class TargetBlank
6
- def initialize(html)
7
- @html = html
8
- end
9
-
7
+ class TargetBlank < HtmlProcessor::Base
10
8
  def process
11
9
  @html.css("a").each do |link|
12
10
  href = link["href"]
13
11
 
14
- next unless href
15
- next if href.start_with?("/", "#", "mailto:")
12
+ next if href.blank? || href.start_with?("/", "#", "mailto:")
16
13
 
17
14
  link["target"] = "_blank"
18
- link["rel"] = "noopener"
19
15
  end
20
16
  end
21
17
  end
@@ -1,28 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "perron/html_processor/target_blank"
4
+ require "perron/html_processor/lazy_load_images"
4
5
 
5
6
  module Perron
6
7
  class HtmlProcessor
7
- def initialize(html)
8
+ def initialize(html, processors: [])
8
9
  @html = html
10
+ @processors = processors.map { find_by(it) }
9
11
  end
10
12
 
11
13
  def process
12
14
  document = Nokogiri::HTML::DocumentFragment.parse(@html)
13
15
 
14
- PROCESSORS.each do |processor|
15
- processor.new(document).process
16
- end
16
+ @processors.each { it.new(document).process }
17
17
 
18
18
  document.to_html
19
19
  end
20
20
 
21
21
  private
22
22
 
23
- # TODO: should be a configuration option
24
- PROCESSORS = [
25
- Perron::HtmlProcessor::TargetBlank
26
- ]
23
+ BUILT_IN = {
24
+ "target_blank" => Perron::HtmlProcessor::TargetBlank,
25
+ "lazy_load_images" => Perron::HtmlProcessor::LazyLoadImages
26
+ }
27
+
28
+ def find_by(identifier)
29
+ case identifier
30
+ when String, Symbol
31
+ key = identifier.to_s
32
+
33
+ BUILT_IN[key] || find_class_by(key)
34
+ when Class
35
+ identifier
36
+ else
37
+ raise Perron::Errors::InvalidProcessorError, "Processor must be a String, Symbol, or Class, but got #{identifier.class.name}."
38
+ end
39
+ end
40
+
41
+ def find_class_by(name)
42
+ processor = name.camelize.safe_constantize
43
+
44
+ return processor if processor
45
+
46
+ raise Perron::Errors::ProcessorNotFoundError,
47
+ "Could not find processor `#{name}`. It is not a Perron-included processor and the constant `#{name.camelize}` could not be found."
48
+ end
27
49
  end
28
50
  end
@@ -5,9 +5,9 @@ require "perron/html_processor"
5
5
  module Perron
6
6
  class Markdown
7
7
  class << self
8
- def render(text)
8
+ def render(text, processors: [])
9
9
  parser.parse(text)
10
- .then { Perron::HtmlProcessor.new(it).process }
10
+ .then { Perron::HtmlProcessor.new(it, processors: processors).process }
11
11
  .html_safe
12
12
  end
13
13
 
@@ -20,6 +20,7 @@ module Perron
20
20
  private
21
21
 
22
22
  FRONTMATTER_KEY_MAP = {
23
+ "locale" => %w[og:locale],
23
24
  "image" => %w[og:image twitter:image],
24
25
  "author" => %w[og:author]
25
26
  }.freeze
@@ -27,33 +28,40 @@ module Perron
27
28
  def tags
28
29
  @tags ||= begin
29
30
  frontmatter = @resource&.metadata&.stringify_keys || {}
30
- defaults = @config.metadata
31
+ collection_config = @resource.collection.configuration.metadata
32
+ site_config = @config.metadata
31
33
 
32
- title = frontmatter["title"] || defaults["title"] || @config.site_name || Rails.application.name.underscore.camelize
33
- description = frontmatter["description"] || defaults["description"]
34
- author = frontmatter["author"] || defaults["author"]
35
- image = frontmatter["image"] || defaults["image"]
36
- og_image = frontmatter["og:image"] || image
37
- twitter_image = frontmatter["twitter:image"] || og_image
34
+ title = frontmatter["title"] || collection_config["title"] || site_config["title"] || @config.site_name || Rails.application.name.underscore.camelize
35
+ type = frontmatter["type"] || collection_config["type"] || site_config["type"]
36
+ description = frontmatter["description"] || collection_config["description"] || site_config["description"]
37
+ logo = frontmatter["logo"] || collection_config["logo"] || site_config["logo"]
38
+ author = frontmatter["author"] || collection_config["author"] || site_config["author"]
39
+ locale = frontmatter["locale"] || collection_config["locale"] || site_config["locale"]
40
+
41
+ image = frontmatter["image"] || collection_config["image"] || site_config["image"]
42
+ og_image = frontmatter["og:image"] || collection_config["og:image"] || site_config["og:image"] || image
43
+ twitter_image = frontmatter["twitter:image"] || collection_config["twitter:image"] || site_config["twitter:image"] || og_image
38
44
 
39
45
  {
40
46
  title: title_tag(title),
41
47
  description: meta_tag(name: "description", content: description),
48
+ article_published: meta_tag(property: "article:published_time", content: @resource&.published_at),
42
49
 
43
- og_type: meta_tag(property: "og:type", content: frontmatter["og:type"] || "article"),
44
50
  og_title: meta_tag(property: "og:title", content: frontmatter["og:title"] || title),
51
+ og_type: meta_tag(property: "og:type", content: frontmatter["og:type"] || type),
52
+ og_url: meta_tag(property: "og:url", content: canonical_url),
53
+ og_image: meta_tag(property: "og:image", content: og_image),
54
+
45
55
  og_description: meta_tag(property: "og:description", content: frontmatter["og:description"] || description),
46
56
  og_site_name: meta_tag(property: "og:site_name", content: @config.site_name),
47
- og_image: meta_tag(property: "og:image", content: og_image),
57
+ og_logo: meta_tag(property: "og:logo", content: frontmatter["og:logo"] || logo),
48
58
  og_author: meta_tag(property: "og:author", content: frontmatter["og:author"] || author),
59
+ og_locale: meta_tag(property: "og:locale", content: frontmatter["og:locale"] || locale),
49
60
 
50
61
  twitter_card: meta_tag(name: "twitter:card", content: frontmatter["twitter:card"] || "summary_large_image"),
51
62
  twitter_title: meta_tag(name: "twitter:title", content: frontmatter["twitter:title"] || title),
52
63
  twitter_description: meta_tag(name: "twitter:description", content: frontmatter["twitter:description"] || description),
53
- twitter_image: meta_tag(name: "twitter:image", content: twitter_image),
54
- article_published: meta_tag(property: "article:published_time", content: @resource&.published_at),
55
-
56
- og_url: meta_tag(property: "og:url", content: canonical_url)
64
+ twitter_image: meta_tag(name: "twitter:image", content: twitter_image)
57
65
  }
58
66
  end
59
67
  end