bridgetown-core 0.20.0 → 0.21.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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/bridgetown-core.gemspec +0 -1
  3. data/lib/bridgetown-core.rb +11 -22
  4. data/lib/bridgetown-core/cleaner.rb +2 -2
  5. data/lib/bridgetown-core/collection.rb +14 -11
  6. data/lib/bridgetown-core/commands/build.rb +0 -11
  7. data/lib/bridgetown-core/commands/concerns/git_helpers.rb +20 -0
  8. data/lib/bridgetown-core/commands/configure.rb +4 -3
  9. data/lib/bridgetown-core/commands/doctor.rb +1 -19
  10. data/lib/bridgetown-core/commands/new.rb +6 -6
  11. data/lib/bridgetown-core/commands/plugins.rb +14 -13
  12. data/lib/bridgetown-core/commands/serve.rb +0 -14
  13. data/lib/bridgetown-core/commands/webpack.rb +75 -0
  14. data/lib/bridgetown-core/commands/webpack/enable-postcss.rb +12 -0
  15. data/lib/bridgetown-core/commands/webpack/setup.rb +4 -0
  16. data/lib/bridgetown-core/commands/webpack/update.rb +3 -0
  17. data/lib/bridgetown-core/commands/webpack/webpack.config.js +18 -0
  18. data/lib/{site_template/webpack.config.js.erb → bridgetown-core/commands/webpack/webpack.defaults.js.erb} +25 -11
  19. data/lib/bridgetown-core/component.rb +183 -0
  20. data/lib/bridgetown-core/concerns/front_matter_importer.rb +52 -0
  21. data/lib/bridgetown-core/concerns/layout_placeable.rb +1 -1
  22. data/lib/bridgetown-core/concerns/site/configurable.rb +3 -7
  23. data/lib/bridgetown-core/concerns/site/content.rb +2 -3
  24. data/lib/bridgetown-core/concerns/site/localizable.rb +3 -5
  25. data/lib/bridgetown-core/concerns/site/processable.rb +5 -4
  26. data/lib/bridgetown-core/concerns/site/writable.rb +1 -1
  27. data/lib/bridgetown-core/concerns/validatable.rb +1 -5
  28. data/lib/bridgetown-core/configuration.rb +22 -14
  29. data/lib/bridgetown-core/configurations/bt-postcss.rb +6 -6
  30. data/lib/bridgetown-core/configurations/netlify.rb +1 -0
  31. data/lib/bridgetown-core/configurations/tailwindcss.rb +6 -6
  32. data/lib/bridgetown-core/converter.rb +9 -0
  33. data/lib/bridgetown-core/converters/erb_templates.rb +51 -35
  34. data/lib/bridgetown-core/converters/liquid_templates.rb +1 -1
  35. data/lib/bridgetown-core/converters/markdown.rb +1 -1
  36. data/lib/bridgetown-core/converters/markdown/kramdown_parser.rb +2 -38
  37. data/lib/bridgetown-core/converters/ruby_templates.rb +17 -0
  38. data/lib/bridgetown-core/converters/smartypants.rb +3 -1
  39. data/lib/bridgetown-core/core_ext/psych.rb +19 -0
  40. data/lib/bridgetown-core/document.rb +3 -2
  41. data/lib/bridgetown-core/drops/relations_drop.rb +23 -0
  42. data/lib/bridgetown-core/drops/resource_drop.rb +3 -1
  43. data/lib/bridgetown-core/drops/unified_payload_drop.rb +1 -0
  44. data/lib/bridgetown-core/entry_filter.rb +7 -5
  45. data/lib/bridgetown-core/filters.rb +1 -25
  46. data/lib/bridgetown-core/filters/from_liquid.rb +23 -0
  47. data/lib/bridgetown-core/filters/url_filters.rb +12 -0
  48. data/lib/bridgetown-core/frontmatter_defaults.rb +1 -1
  49. data/lib/bridgetown-core/generators/prototype_generator.rb +25 -4
  50. data/lib/bridgetown-core/helpers.rb +48 -9
  51. data/lib/bridgetown-core/layout.rb +52 -20
  52. data/lib/bridgetown-core/model/origin.rb +1 -1
  53. data/lib/bridgetown-core/model/{file_origin.rb → repo_origin.rb} +33 -26
  54. data/lib/bridgetown-core/page.rb +2 -1
  55. data/lib/bridgetown-core/plugin_manager.rb +1 -1
  56. data/lib/bridgetown-core/publisher.rb +2 -2
  57. data/lib/bridgetown-core/reader.rb +13 -11
  58. data/lib/bridgetown-core/readers/data_reader.rb +2 -1
  59. data/lib/bridgetown-core/readers/defaults_reader.rb +1 -1
  60. data/lib/bridgetown-core/readers/layout_reader.rb +1 -1
  61. data/lib/bridgetown-core/readers/page_reader.rb +1 -0
  62. data/lib/bridgetown-core/readers/post_reader.rb +5 -4
  63. data/lib/bridgetown-core/regenerator.rb +1 -1
  64. data/lib/bridgetown-core/related_posts.rb +8 -5
  65. data/lib/bridgetown-core/renderer.rb +1 -1
  66. data/lib/bridgetown-core/resource/base.rb +80 -26
  67. data/lib/bridgetown-core/resource/permalink_processor.rb +1 -1
  68. data/lib/bridgetown-core/resource/relations.rb +132 -0
  69. data/lib/bridgetown-core/resource/taxonomy_term.rb +10 -1
  70. data/lib/bridgetown-core/resource/taxonomy_type.rb +9 -0
  71. data/lib/bridgetown-core/resource/transformer.rb +18 -14
  72. data/lib/bridgetown-core/ruby_template_view.rb +7 -11
  73. data/lib/bridgetown-core/site.rb +4 -5
  74. data/lib/bridgetown-core/tags/highlight.rb +2 -15
  75. data/lib/bridgetown-core/tags/include.rb +1 -1
  76. data/lib/bridgetown-core/tags/post_url.rb +2 -2
  77. data/lib/bridgetown-core/utils.rb +9 -3
  78. data/lib/bridgetown-core/utils/require_gems.rb +60 -0
  79. data/lib/bridgetown-core/utils/ruby_exec.rb +6 -9
  80. data/lib/bridgetown-core/utils/ruby_front_matter.rb +39 -0
  81. data/lib/bridgetown-core/version.rb +2 -2
  82. data/lib/bridgetown-core/watcher.rb +2 -1
  83. data/lib/bridgetown-core/yaml_parser.rb +22 -0
  84. data/lib/site_template/config/.keep +0 -0
  85. data/lib/site_template/package.json.erb +4 -4
  86. data/lib/site_template/plugins/site_builder.rb +1 -1
  87. data/lib/site_template/src/_posts/0000-00-00-welcome-to-bridgetown.md.erb +1 -1
  88. metadata +21 -23
  89. data/lib/bridgetown-core/external.rb +0 -58
  90. data/lib/bridgetown-core/path_manager.rb +0 -31
  91. data/lib/bridgetown-core/readers/static_file_reader.rb +0 -25
  92. data/lib/bridgetown-core/utils/platforms.rb +0 -81
  93. data/lib/bridgetown-core/utils/thread_event.rb +0 -31
@@ -72,21 +72,11 @@ module Bridgetown
72
72
  attr_reader :extractions
73
73
 
74
74
  def initialize(config)
75
- @main_fallback_highlighter = config["highlighter"] || "rouge"
76
75
  @config = config["kramdown"] || {}
77
- @highlighter = nil
78
- setup
79
- load_dependencies
80
- end
81
-
82
- # Setup and normalize the configuration:
83
- # * Create Kramdown if it doesn't exist.
84
- # * Set syntax_highlighter
85
- # * Make sure `syntax_highlighter_opts` exists.
86
- def setup
87
- @config["syntax_highlighter"] ||= highlighter
76
+ @config["syntax_highlighter"] ||= config["highlighter"] || "rouge"
88
77
  @config["syntax_highlighter_opts"] ||= {}
89
78
  @config["syntax_highlighter_opts"]["guess_lang"] = @config["guess_lang"]
79
+ require "kramdown-parser-gfm" if @config["input"] == "GFM"
90
80
  end
91
81
 
92
82
  def convert(content)
@@ -100,32 +90,6 @@ module Bridgetown
100
90
  @extractions = document.root.options[:extractions] # could be nil
101
91
  html_output
102
92
  end
103
-
104
- private
105
-
106
- def load_dependencies
107
- require "kramdown-parser-gfm" if @config["input"] == "GFM"
108
-
109
- # `mathjax` emgine is bundled within kramdown-2.x and will be handled by
110
- # kramdown itself.
111
- if (math_engine = @config["math_engine"]) && math_engine != "mathjax"
112
- Bridgetown::External.require_with_graceful_fail("kramdown-math-#{math_engine}")
113
- end
114
- end
115
-
116
- # config[kramdown][syntax_higlighter] >
117
- # config[highlighter]
118
- def highlighter
119
- return @highlighter if @highlighter
120
-
121
- if @config["syntax_highlighter"]
122
- return @highlighter = @config[
123
- "syntax_highlighter"
124
- ]
125
- end
126
-
127
- @highlighter = @main_fallback_highlighter
128
- end
129
93
  end
130
94
  end
131
95
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Converters
5
+ class RubyTemplates < Converter
6
+ priority :highest
7
+ input :rb
8
+
9
+ def convert(content, convertible)
10
+ erb_view = Bridgetown::ERBView.new(convertible)
11
+ erb_view.instance_eval(
12
+ content, convertible.relative_path.to_s, line_start(convertible)
13
+ ).to_s
14
+ end
15
+ end
16
+ end
17
+ end
@@ -25,7 +25,9 @@ module Bridgetown
25
25
  priority :low
26
26
 
27
27
  def initialize(config)
28
- Bridgetown::External.require_with_graceful_fail "kramdown" unless defined?(Kramdown)
28
+ unless defined?(Kramdown)
29
+ Bridgetown::Utils::RequireGems.require_with_graceful_fail "kramdown"
30
+ end
29
31
  @config = config["kramdown"].dup || {}
30
32
  @config[:input] = :SmartyPants
31
33
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module CoreExt
5
+ module Psych
6
+ module SafeLoadFile
7
+ def safe_load_file(filename, **kwargs)
8
+ File.open(filename, "r:bom|utf-8") do |f|
9
+ if RUBY_VERSION.start_with?("2.5")
10
+ safe_load f, kwargs[:permitted_classes], [], false, filename
11
+ else
12
+ safe_load f, filename: filename, **kwargs
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bridgetown
4
+ # TODO: to be retired once the Resource engine is made official
4
5
  class Document
5
6
  extend Forwardable
6
7
  include DataAccessible
@@ -223,7 +224,7 @@ module Bridgetown
223
224
  Bridgetown.logger.debug "Reading:", relative_path
224
225
 
225
226
  if yaml_file?
226
- @data = SafeYAML.load_file(path)
227
+ @data = YAMLParser.load_file(path)
227
228
  else
228
229
  begin
229
230
  merge_defaults
@@ -357,7 +358,7 @@ module Bridgetown
357
358
  self.content = File.read(path, **Utils.merged_file_read_opts(site, opts))
358
359
  if content =~ YAML_FRONT_MATTER_REGEXP
359
360
  self.content = $POSTMATCH
360
- data_file = SafeYAML.load(Regexp.last_match(1))
361
+ data_file = YAMLParser.load(Regexp.last_match(1))
361
362
  merge_data!(data_file, source: "YAML front matter") if data_file
362
363
  end
363
364
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Drops
5
+ class RelationsDrop < Drop
6
+ mutable false
7
+
8
+ def [](type)
9
+ return nil unless type.to_s.in?(@obj.relation_types)
10
+
11
+ @obj.resources_for_type(type)
12
+ end
13
+
14
+ def key?(type)
15
+ type.to_s.in?(@obj.relation_types)
16
+ end
17
+
18
+ def fallback_data
19
+ {}
20
+ end
21
+ end
22
+ end
23
+ end
@@ -14,6 +14,7 @@ module Bridgetown
14
14
  def_delegator :@obj, :relative_path, :path
15
15
  def_delegators :@obj,
16
16
  :id,
17
+ :data,
17
18
  :output,
18
19
  :content,
19
20
  :to_s,
@@ -21,7 +22,8 @@ module Bridgetown
21
22
  :relative_path,
22
23
  :relative_url,
23
24
  :date,
24
- :taxonomies
25
+ :taxonomies,
26
+ :relations
25
27
 
26
28
  private def_delegator :@obj, :data, :fallback_data
27
29
 
@@ -6,6 +6,7 @@ module Bridgetown
6
6
  mutable true
7
7
 
8
8
  attr_accessor :page, :layout, :content, :paginator
9
+ alias_method :resource, :page
9
10
 
10
11
  def bridgetown
11
12
  BridgetownDrop.global
@@ -43,8 +43,8 @@ module Bridgetown
43
43
  end
44
44
 
45
45
  def included?(entry)
46
- glob_include?(site.include, entry) ||
47
- glob_include?(site.include, File.basename(entry))
46
+ glob_include?(site.config.include, entry) ||
47
+ glob_include?(site.config.include, File.basename(entry))
48
48
  end
49
49
 
50
50
  def special?(entry)
@@ -62,7 +62,9 @@ module Bridgetown
62
62
  end
63
63
 
64
64
  def excluded?(entry)
65
- glob_include?(site.exclude - site.include, relative_to_source(entry)).tap do |excluded|
65
+ glob_include?(
66
+ site.config.exclude - site.config.include, relative_to_source(entry)
67
+ ).tap do |excluded|
66
68
  if excluded
67
69
  Bridgetown.logger.debug(
68
70
  "EntryFilter:",
@@ -75,12 +77,12 @@ module Bridgetown
75
77
  # Check if an entry matches a specific pattern.
76
78
  # Returns true if path matches against any glob pattern, else false.
77
79
  def glob_include?(enumerator, entry)
78
- entry_with_source = PathManager.join(site.source, entry)
80
+ entry_with_source = File.join(site.source, entry)
79
81
 
80
82
  enumerator.any? do |pattern|
81
83
  case pattern
82
84
  when String
83
- pattern_with_source = PathManager.join(site.source, pattern)
85
+ pattern_with_source = File.join(site.source, pattern)
84
86
 
85
87
  File.fnmatch?(pattern_with_source, entry_with_source) ||
86
88
  entry_with_source.start_with?(pattern_with_source)
@@ -31,30 +31,6 @@ module Bridgetown
31
31
  ).convert(input.to_s)
32
32
  end
33
33
 
34
- # TODO: This should be removed, there is no Sass converter
35
- # Convert a Sass string into CSS output.
36
- #
37
- # input - The Sass String to convert.
38
- #
39
- # Returns the CSS formatted String.
40
- def sassify(input)
41
- @context.registers[:site].find_converter_instance(
42
- Bridgetown::Converters::Sass
43
- ).convert(input)
44
- end
45
-
46
- # TODO: This should be removed, there is no Scss converter
47
- # Convert a Scss string into CSS output.
48
- #
49
- # input - The Scss String to convert.
50
- #
51
- # Returns the CSS formatted String.
52
- def scssify(input)
53
- @context.registers[:site].find_converter_instance(
54
- Bridgetown::Converters::Scss
55
- ).convert(input)
56
- end
57
-
58
34
  # Slugify a filename or title.
59
35
  #
60
36
  # input - The filename or title to slugify.
@@ -132,7 +108,7 @@ module Bridgetown
132
108
  script += "function(c){{var j=c.charCodeAt(0);if((j>=33)&&(j<=126)){"
133
109
  script += "return String.fromCharCode(33+((j+ 14)%94));}"
134
110
  script += "else{return String.fromCharCode(j);}}}));</script>"
135
- script
111
+ script.html_safe
136
112
  end
137
113
 
138
114
  # Replace any whitespace in the input string with a single space
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Filters
5
+ module FromLiquid
6
+ extend Liquid::StandardFilters
7
+
8
+ def strip_html(input)
9
+ FromLiquid.strip_html(input)
10
+ end
11
+
12
+ def strip_newlines(input)
13
+ FromLiquid.strip_newlines(input)
14
+ end
15
+
16
+ def newline_to_br(input)
17
+ FromLiquid.newline_to_br(input)
18
+ end
19
+
20
+ # FYI, truncate and truncate words are already provided by ActiveSupport! =)
21
+ end
22
+ end
23
+ end
@@ -3,6 +3,8 @@
3
3
  module Bridgetown
4
4
  module Filters
5
5
  module URLFilters
6
+ extend self
7
+
6
8
  # Produces an absolute URL based on site.url and site.baseurl.
7
9
  #
8
10
  # input - the URL to make absolute.
@@ -35,6 +37,16 @@ module Bridgetown
35
37
  input.sub(%r!/index\.html?$!, "/")
36
38
  end
37
39
 
40
+ # Strips the extension (if present) off a path/URL
41
+ #
42
+ # @param input [Object] value which responds to `to_s`
43
+ # @return [String]
44
+ def strip_extname(input)
45
+ Pathname.new(input.to_s).yield_self do |path|
46
+ path.dirname + path.basename(".*")
47
+ end.to_s
48
+ end
49
+
38
50
  private
39
51
 
40
52
  def compute_absolute_url(input)
@@ -13,7 +13,7 @@ module Bridgetown
13
13
  end
14
14
 
15
15
  def reset
16
- @glob_cache = {} if @glob_cache
16
+ @glob_cache = {}
17
17
  end
18
18
 
19
19
  def update_deprecated_types(set)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Handles Legacy Pages
3
+ # Handles Generated Pages
4
4
  Bridgetown::Hooks.register :pages, :post_init, reloadable: false do |page|
5
5
  if page.class != Bridgetown::PrototypePage && page.data["prototype"].is_a?(Hash)
6
6
  Bridgetown::PrototypeGenerator.add_matching_template(page)
@@ -14,6 +14,11 @@ Bridgetown::Hooks.register :resources, :post_read, reloadable: false do |resourc
14
14
  end
15
15
  end
16
16
 
17
+ # Ensure sites clear out templates before rebuild
18
+ Bridgetown::Hooks.register :site, :after_reset, reloadable: false do |_site|
19
+ Bridgetown::PrototypeGenerator.matching_templates.clear
20
+ end
21
+
17
22
  module Bridgetown
18
23
  class PrototypeGenerator < Generator
19
24
  priority :low
@@ -21,9 +26,9 @@ module Bridgetown
21
26
  # @return [Bridgetown::Site]
22
27
  attr_reader :site
23
28
 
24
- # @return [Array<Bridgetown::Page, Bridgetown::Resource::Base>]
29
+ # @return [Set<Bridgetown::Page, Bridgetown::Resource::Base>]
25
30
  def self.matching_templates
26
- @matching_templates ||= []
31
+ @matching_templates ||= Set.new
27
32
  end
28
33
 
29
34
  def self.add_matching_template(template)
@@ -41,6 +46,8 @@ module Bridgetown
41
46
  end
42
47
 
43
48
  if prototype_pages.length.positive?
49
+ ensure_pagination_enabled
50
+
44
51
  page_list.reject! do |page|
45
52
  prototype_pages.include? page
46
53
  end
@@ -56,6 +63,15 @@ module Bridgetown
56
63
  end
57
64
  end
58
65
 
66
+ def ensure_pagination_enabled
67
+ unless @site.config.dig(:pagination, :enabled)
68
+ Bridgetown.logger.warn(
69
+ "Pagination:",
70
+ "Must be enabled for prototype pages to contain matches"
71
+ )
72
+ end
73
+ end
74
+
59
75
  # Check incoming prototype configuration and normalize options.
60
76
  #
61
77
  # @param prototype_page [Bridgetown::Page, Bridgetown::Resource::Base]
@@ -70,7 +86,12 @@ module Bridgetown
70
86
  @configured_collection = prototype_page.data["prototype"]["collection"]
71
87
  end
72
88
 
73
- return nil unless site.collections[@configured_collection]
89
+ unless site.collections[@configured_collection]
90
+ Bridgetown.logger.warn(
91
+ "No collection specified for prototype page #{prototype_page.relative_path}"
92
+ )
93
+ return nil
94
+ end
74
95
 
75
96
  # Categories and Tags are unique in that singular and plural front matter
76
97
  # can be present for each
@@ -4,8 +4,13 @@ module Bridgetown
4
4
  class RubyTemplateView
5
5
  class Helpers
6
6
  include Bridgetown::Filters
7
+ include Bridgetown::Filters::FromLiquid
7
8
 
8
- attr_reader :view, :site
9
+ # @return [Bridgetown::RubyTemplateView]
10
+ attr_reader :view
11
+
12
+ # @return [Bridgetown::Site]
13
+ attr_reader :site
9
14
 
10
15
  Context = Struct.new(:registers)
11
16
 
@@ -28,20 +33,44 @@ module Bridgetown
28
33
  pairs.select { |_key, truthy| truthy }.keys.join(" ")
29
34
  end
30
35
 
36
+ # Convert a Markdown string into HTML output.
37
+ #
38
+ # @param input [String] the Markdown to convert, if no block is passed
39
+ # @return [String]
40
+ def markdownify(input = nil, &block)
41
+ content = Bridgetown::Utils.reindent_for_markdown(
42
+ block.nil? ? input.to_s : view.capture(&block)
43
+ )
44
+ converter = site.find_converter_instance(Bridgetown::Converters::Markdown)
45
+ safe(converter.convert(content).strip)
46
+ end
47
+
31
48
  # This helper will generate the correct permalink URL for the file path.
32
49
  #
33
50
  # @param relative_path [String, Object] source file path, e.g.
34
- # "_posts/2020-10-20-my-post.md", or object that responds to `url`
51
+ # "_posts/2020-10-20-my-post.md", or object that responds to either
52
+ # `url` or `relative_url`
35
53
  # @return [String] the permalink URL for the file
36
- # @raise [ArgumentError] if the file cannot be found
37
54
  def url_for(relative_path)
38
- path_string = !relative_path.is_a?(String) ? relative_path.url : relative_path
55
+ if relative_path.respond_to?(:relative_url)
56
+ return safe(relative_path.relative_url) # new resource engine
57
+ elsif relative_path.respond_to?(:url)
58
+ return safe(relative_url(relative_path.url)) # old legacy engine
59
+ elsif relative_path.start_with?("/", "http")
60
+ return safe(relative_path)
61
+ end
39
62
 
40
- return path_string if path_string.start_with?("/", "http")
63
+ find_relative_url_for_path(relative_path)
64
+ end
65
+ alias_method :link, :url_for
41
66
 
67
+ # @param relative_path [String] source file path, e.g.
68
+ # "_posts/2020-10-20-my-post.md"
69
+ # @raise [ArgumentError] if the file cannot be found
70
+ def find_relative_url_for_path(relative_path)
42
71
  site.each_site_file do |item|
43
- if item.relative_path == path_string || item.relative_path == "/#{path_string}"
44
- return relative_url(item)
72
+ if item.relative_path == relative_path || item.relative_path == "/#{relative_path}"
73
+ return safe(item.respond_to?(:relative_url) ? item.relative_url : relative_url(item))
45
74
  end
46
75
  end
47
76
 
@@ -51,7 +80,6 @@ module Bridgetown
51
80
  Make sure the document exists and the path is correct.
52
81
  MSG
53
82
  end
54
- alias_method :link, :url_for
55
83
 
56
84
  # This helper will generate the correct permalink URL for the file path.
57
85
  #
@@ -69,7 +97,8 @@ module Bridgetown
69
97
  attr = attr.to_s.tr("_", "-")
70
98
  segments << "#{attr}=\"#{Utils.xml_escape(option)}\""
71
99
  end
72
- "<#{segments.join(" ")}>#{text}</a>"
100
+ # TODO: this might leak an XSS string into text, need to check
101
+ safe("<#{segments.join(" ")}>#{text}</a>")
73
102
  end
74
103
 
75
104
  # Forward all arguments to I18n.t method
@@ -79,6 +108,16 @@ module Bridgetown
79
108
  def t(*args)
80
109
  I18n.send :t, *args
81
110
  end
111
+
112
+ # For template contexts where ActiveSupport's output safety is loaded, we
113
+ # can ensure a string has been marked safe
114
+ #
115
+ # @param input [Object]
116
+ # @return [String]
117
+ def safe(input)
118
+ input.to_s.html_safe
119
+ end
120
+ alias_method :raw, :safe
82
121
  end
83
122
  end
84
123
  end