bridgetown-core 1.0.0.beta3 → 1.1.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -1
  3. data/lib/bridgetown-core/collection.rb +37 -20
  4. data/lib/bridgetown-core/commands/concerns/actions.rb +3 -2
  5. data/lib/bridgetown-core/commands/configure.rb +1 -1
  6. data/lib/bridgetown-core/commands/esbuild/esbuild.config.js +20 -16
  7. data/lib/bridgetown-core/commands/esbuild/esbuild.defaults.js.erb +95 -12
  8. data/lib/bridgetown-core/commands/new.rb +10 -9
  9. data/lib/bridgetown-core/commands/plugins.rb +2 -0
  10. data/lib/bridgetown-core/commands/start.rb +3 -0
  11. data/lib/bridgetown-core/commands/webpack/update.rb +3 -3
  12. data/lib/bridgetown-core/commands/webpack/webpack.defaults.js.erb +19 -14
  13. data/lib/bridgetown-core/component.rb +13 -7
  14. data/lib/bridgetown-core/concerns/localizable.rb +20 -0
  15. data/lib/bridgetown-core/concerns/prioritizable.rb +44 -0
  16. data/lib/bridgetown-core/concerns/publishable.rb +11 -1
  17. data/lib/bridgetown-core/concerns/site/configurable.rb +2 -10
  18. data/lib/bridgetown-core/concerns/site/localizable.rb +5 -1
  19. data/lib/bridgetown-core/concerns/site/ssr.rb +3 -3
  20. data/lib/bridgetown-core/concerns/site/writable.rb +28 -0
  21. data/lib/bridgetown-core/configuration.rb +2 -0
  22. data/lib/bridgetown-core/configurations/bt-postcss/postcss.config.js +5 -3
  23. data/lib/bridgetown-core/configurations/bt-postcss.rb +1 -1
  24. data/lib/bridgetown-core/configurations/gh-pages/gh-pages.yml +33 -0
  25. data/lib/bridgetown-core/configurations/gh-pages.rb +16 -0
  26. data/lib/bridgetown-core/configurations/lit/esbuild-plugins.js +21 -0
  27. data/lib/bridgetown-core/configurations/lit/happy-days.lit.js +26 -0
  28. data/lib/bridgetown-core/configurations/lit/lit-components-entry.js +1 -0
  29. data/lib/bridgetown-core/configurations/lit/lit-ssr.config.js +6 -0
  30. data/lib/bridgetown-core/configurations/lit.rb +95 -0
  31. data/lib/bridgetown-core/configurations/open-props/variables.css.erb +11 -0
  32. data/lib/bridgetown-core/configurations/open-props.rb +21 -0
  33. data/lib/bridgetown-core/configurations/ruby2js/hello_world.js.rb +9 -0
  34. data/lib/bridgetown-core/configurations/ruby2js.rb +67 -0
  35. data/lib/bridgetown-core/configurations/shoelace.rb +50 -0
  36. data/lib/bridgetown-core/configurations/tailwindcss.rb +16 -2
  37. data/lib/bridgetown-core/converters/markdown/kramdown_parser.rb +1 -1
  38. data/lib/bridgetown-core/drops/generated_page_drop.rb +2 -1
  39. data/lib/bridgetown-core/drops/resource_drop.rb +2 -1
  40. data/lib/bridgetown-core/errors.rb +5 -5
  41. data/lib/bridgetown-core/filters/translation_filters.rb +11 -0
  42. data/lib/bridgetown-core/filters/url_filters.rb +37 -10
  43. data/lib/bridgetown-core/filters.rb +3 -0
  44. data/lib/bridgetown-core/frontmatter_defaults.rb +14 -8
  45. data/lib/bridgetown-core/generated_page.rb +1 -0
  46. data/lib/bridgetown-core/kramdown/parser/gfm.rb +36 -0
  47. data/lib/bridgetown-core/model/base.rb +1 -2
  48. data/lib/bridgetown-core/plugin.rb +6 -37
  49. data/lib/bridgetown-core/plugin_manager.rb +3 -2
  50. data/lib/bridgetown-core/rack/boot.rb +5 -0
  51. data/lib/bridgetown-core/rack/logger.rb +14 -4
  52. data/lib/bridgetown-core/rack/roda.rb +102 -10
  53. data/lib/bridgetown-core/rack/routes.rb +87 -6
  54. data/lib/bridgetown-core/resource/base.rb +4 -6
  55. data/lib/bridgetown-core/resource/destination.rb +18 -0
  56. data/lib/bridgetown-core/resource/permalink_processor.rb +6 -4
  57. data/lib/bridgetown-core/resource/relations.rb +1 -1
  58. data/lib/bridgetown-core/utils/aux.rb +2 -1
  59. data/lib/bridgetown-core/utils/require_gems.rb +3 -6
  60. data/lib/bridgetown-core/utils.rb +25 -12
  61. data/lib/bridgetown-core/version.rb +2 -2
  62. data/lib/bridgetown-core/watcher.rb +19 -6
  63. data/lib/bridgetown-core.rb +8 -2
  64. data/lib/site_template/Gemfile.erb +1 -1
  65. data/lib/site_template/README.md +2 -2
  66. data/lib/site_template/TEMPLATES/erb/_components/shared/navbar.erb +4 -4
  67. data/lib/site_template/TEMPLATES/liquid/_components/navbar.liquid +4 -4
  68. data/lib/site_template/TEMPLATES/serbea/_components/shared/navbar.serb +4 -4
  69. data/lib/site_template/bridgetown.config.yml +10 -3
  70. data/lib/site_template/frontend/javascript/index.js.erb +1 -0
  71. data/lib/site_template/frontend/styles/syntax-highlighting.css +77 -0
  72. data/lib/site_template/package.json.erb +18 -18
  73. data/lib/site_template/server/roda_app.rb +3 -6
  74. data/lib/site_template/src/404.html +2 -1
  75. data/lib/site_template/src/500.html +10 -0
  76. metadata +20 -3
  77. data/lib/bridgetown-core/publisher.rb +0 -29
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Filters
5
+ module TranslationFilters
6
+ def t(input)
7
+ I18n.t(input.to_s)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -7,9 +7,8 @@ module Bridgetown
7
7
 
8
8
  # Produces an absolute URL based on site.url and site.base_path.
9
9
  #
10
- # input - the URL to make absolute.
11
- #
12
- # Returns the absolute URL as a String.
10
+ # @param input [String] the URL to make absolute.
11
+ # @return [String] the absolute URL as a String.
13
12
  def absolute_url(input)
14
13
  cache = (@context.registers[:cached_absolute_urls] ||= {})
15
14
  cache[input] ||= compute_absolute_url(input)
@@ -18,19 +17,47 @@ module Bridgetown
18
17
  # Produces a URL relative to the domain root based on site.base_path
19
18
  # unless it is already an absolute url with an authority (host).
20
19
  #
21
- # input - the URL to make relative to the domain root
22
- #
23
- # Returns a URL relative to the domain root as a String.
20
+ # @param input [String] the URL to make relative to the domain root
21
+ # @return [String] a URL relative to the domain root as a String.
24
22
  def relative_url(input)
25
23
  cache = (@context.registers[:cached_relative_urls] ||= {})
26
24
  cache[input] ||= compute_relative_url(input)
27
25
  end
28
26
 
29
- # Strips trailing `/index.html` from URLs to create pretty permalinks
27
+ # For string input, adds a prefix of the current site locale to a relative
28
+ # URL, unless it's a default locale and prefix_current_locale config is
29
+ # false. For a resources array input, return a filtered resources array
30
+ # based on the locale.
30
31
  #
31
- # input - the URL with a possible `/index.html`
32
+ # @param input [String, Array] the relative URL, or an array of resources
33
+ # @param use_locale [String] another locale to use beside the current one
34
+ # (must be in site's `available_locales` config)
35
+ # @return [String, Array] the prefixed relative URL, or filtered resources
36
+ def in_locale(input, use_locale = nil)
37
+ site = @context.registers[:site]
38
+ use_locale ||= site.locale
39
+
40
+ # If we're given a collection, filter down and return
41
+ if input.is_a?(Array)
42
+ return input.select do |res|
43
+ res.data[:locale].to_sym == use_locale.to_sym
44
+ end
45
+ end
46
+
47
+ if !site.config.prefix_default_locale &&
48
+ use_locale.to_sym == site.config.default_locale
49
+ return input
50
+ end
51
+
52
+ return input unless site.config.available_locales.include?(use_locale.to_sym)
53
+
54
+ "#{use_locale}/#{input.to_s.delete_prefix("/")}"
55
+ end
56
+
57
+ # Strips trailing `/index.html` from URLs to create pretty permalinks
32
58
  #
33
- # Returns a URL with the trailing `/index.html` removed
59
+ # @param input [String] the URL with a possible `/index.html`
60
+ # @return [String] a URL with the trailing `/index.html` removed
34
61
  def strip_index(input)
35
62
  return if input.nil? || input.to_s.empty?
36
63
 
@@ -42,7 +69,7 @@ module Bridgetown
42
69
  # @param input [Object] value which responds to `to_s`
43
70
  # @return [String]
44
71
  def strip_extname(input)
45
- Pathname.new(input.to_s).yield_self do |path|
72
+ Pathname.new(input.to_s).then do |path|
46
73
  path.dirname + path.basename(".*")
47
74
  end.to_s
48
75
  end
@@ -435,3 +435,6 @@ end
435
435
  Liquid::Template.register_filter(
436
436
  Bridgetown::Filters
437
437
  )
438
+ Liquid::Template.register_filter(
439
+ Bridgetown::Filters::TranslationFilters
440
+ )
@@ -9,10 +9,12 @@ module Bridgetown
9
9
 
10
10
  def initialize(site)
11
11
  @site = site
12
+ @defaults_cache = {}
12
13
  end
13
14
 
14
15
  def reset
15
16
  @glob_cache = {}
17
+ @defaults_cache = {}
16
18
  end
17
19
 
18
20
  def ensure_time!(set)
@@ -29,16 +31,19 @@ module Bridgetown
29
31
  # Collects a hash with all default values for a resource
30
32
  #
31
33
  # @param path [String] the relative path of the resource
32
- # @param collection [Symbol] :posts, :pages, etc.
34
+ # @param collection_name [Symbol] :posts, :pages, etc.
33
35
  #
34
- # @returns [Hash] all default values (an empty hash if there are none)
35
- def all(path, collection)
36
- defaults = {}
36
+ # @return [Hash] all default values (an empty hash if there are none)
37
+ def all(path, collection_name)
38
+ if @defaults_cache.key?([path, collection_name])
39
+ return @defaults_cache[[path, collection_name]]
40
+ end
37
41
 
42
+ defaults = {}
38
43
  merge_data_cascade_for_path(path, defaults)
39
44
 
40
45
  old_scope = nil
41
- matching_sets(path, collection).each do |set|
46
+ matching_sets(path, collection_name).each do |set|
42
47
  if has_precedence?(old_scope, set["scope"])
43
48
  defaults = Utils.deep_merge_hashes(defaults, set["values"])
44
49
  old_scope = set["scope"]
@@ -46,7 +51,8 @@ module Bridgetown
46
51
  defaults = Utils.deep_merge_hashes(set["values"], defaults)
47
52
  end
48
53
  end
49
- defaults
54
+
55
+ @defaults_cache[[path, collection_name]] = defaults
50
56
  end
51
57
 
52
58
  private
@@ -123,7 +129,7 @@ module Bridgetown
123
129
  # @param scope [Hash] the defaults set being asked about
124
130
  # @param collection [Symbol] the collection of the resource being processed
125
131
  #
126
- # @returns [Boolean] whether either of the above conditions are satisfied
132
+ # @return [Boolean] whether either of the above conditions are satisfied
127
133
  def applies_collection?(scope, collection)
128
134
  !scope.key?("collection") || scope["collection"].eql?(collection.to_s)
129
135
  end
@@ -132,7 +138,7 @@ module Bridgetown
132
138
  #
133
139
  # @param set [Hash] the default value hash as defined in bridgetown.config.yml
134
140
  #
135
- # @returns [Boolean] if the set is valid and can be used
141
+ # @return [Boolean] if the set is valid and can be used
136
142
  def valid?(set)
137
143
  set.is_a?(Hash) && set["values"].is_a?(Hash)
138
144
  end
@@ -4,6 +4,7 @@ module Bridgetown
4
4
  class GeneratedPage
5
5
  include LayoutPlaceable
6
6
  include LiquidRenderable
7
+ include Localizable
7
8
  include Publishable
8
9
  include Transformable
9
10
 
@@ -0,0 +1,36 @@
1
+ # Frozen-string-literal: true
2
+
3
+ require "kramdown-parser-gfm"
4
+
5
+ module Kramdown
6
+ module Parser
7
+ class GFM
8
+ MARK_DELIMITER = %r{(==|::)+}.freeze
9
+ MARK_MATCH = %r{#{MARK_DELIMITER}(?!\s|=|:).*?[^\s=:]#{MARK_DELIMITER}}m.freeze
10
+
11
+ # Monkey-patch GFM initializer to add our new mark parser
12
+ alias_method :_old_initialize, :initialize
13
+ def initialize(source, options)
14
+ _old_initialize(source, options)
15
+ @span_parsers << :mark if @options[:mark_highlighting]
16
+ end
17
+
18
+ def parse_mark
19
+ line_number = @src.current_line_number
20
+
21
+ @src.pos += @src.matched_size
22
+ el = Element.new(:html_element, "mark", {}, category: :span, line: line_number)
23
+ @tree.children << el
24
+
25
+ env = save_env
26
+ reset_env(src: Kramdown::Utils::StringScanner.new(@src.matched[2..-3], line_number),
27
+ text_type: :text)
28
+ parse_spans(el)
29
+ restore_env(env)
30
+
31
+ el
32
+ end
33
+ define_parser(:mark, MARK_MATCH)
34
+ end
35
+ end
36
+ end
@@ -95,8 +95,7 @@ module Bridgetown
95
95
 
96
96
  # @return [Bridgetown::Resource::Base]
97
97
  def as_resource_in_collection
98
- collection.resources << to_resource.read!
99
- collection.resources.last
98
+ collection.add_resource_from_model(self)
100
99
  end
101
100
 
102
101
  # @return [Bridgetown::Resource::Base]
@@ -3,55 +3,24 @@
3
3
  module Bridgetown
4
4
  class Plugin
5
5
  extend ActiveSupport::DescendantsTracker
6
+ include Bridgetown::Prioritizable
6
7
 
7
- PRIORITIES = {
8
- low: -10,
8
+ self.priorities = {
9
9
  highest: 100,
10
- lowest: -100,
11
- normal: 0,
12
10
  high: 10,
11
+ normal: 0,
12
+ low: -10,
13
+ lowest: -100,
13
14
  }.freeze
14
15
 
15
16
  SourceManifest = Struct.new(:origin, :components, :content, :layouts, keyword_init: true)
16
17
 
17
- # Get or set the priority of this plugin. When called without an
18
- # argument it returns the priority. When an argument is given, it will
19
- # set the priority.
20
- #
21
- # priority - The Symbol priority (default: nil). Valid options are:
22
- # :lowest, :low, :normal, :high, :highest
23
- #
24
- # Returns the Symbol priority.
25
- def self.priority(priority = nil)
26
- @priority ||= nil
27
- @priority = priority if priority && PRIORITIES.key?(priority)
28
- @priority || :normal
29
- end
30
-
31
- # Spaceship is priority [higher -> lower]
32
- #
33
- # other - The class to be compared.
34
- #
35
- # Returns -1, 0, 1.
36
- def self.<=>(other)
37
- PRIORITIES[other.priority] <=> PRIORITIES[priority]
38
- end
39
-
40
- # Spaceship is priority [higher -> lower]
41
- #
42
- # other - The class to be compared.
43
- #
44
- # Returns -1, 0, 1.
45
- def <=>(other)
46
- self.class <=> other.class
47
- end
48
-
49
18
  # Initialize a new plugin. This should be overridden by the subclass.
50
19
  #
51
20
  # config - The Hash of configuration options.
52
21
  #
53
22
  # Returns a new instance.
54
- def initialize(config = {})
23
+ def initialize(config = {}) # rubocop:disable Style/RedundantInitialize
55
24
  # no-op for default
56
25
  end
57
26
  end
@@ -40,7 +40,8 @@ module Bridgetown
40
40
  @loaders_manager = Bridgetown::Utils::LoadersManager.new(site.config)
41
41
  end
42
42
 
43
- def self.require_from_bundler
43
+ def self.require_from_bundler(skip_yarn: false) # rubocop:todo Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
44
+ # NOTE: investigate why this ENV var is really necessary
44
45
  if !ENV["BRIDGETOWN_NO_BUNDLER_REQUIRE"] && File.file?("Gemfile")
45
46
  require "bundler"
46
47
 
@@ -48,7 +49,7 @@ module Bridgetown
48
49
  (dep.groups & [PLUGINS_GROUP]).any? && dep.should_include?
49
50
  end
50
51
 
51
- install_yarn_dependencies(required_gems)
52
+ install_yarn_dependencies(required_gems) unless skip_yarn
52
53
 
53
54
  required_gems.each do |installed_gem|
54
55
  add_registered_plugin installed_gem
@@ -19,6 +19,11 @@ module Bridgetown
19
19
  attr_accessor :loaders_manager
20
20
  end
21
21
 
22
+ # Start up the Roda Rack application and the Zeitwerk autoloaders. Ensure the
23
+ # Roda app is provided the preloaded Bridgetown site configuration. Handle
24
+ # any uncaught Roda errors.
25
+ #
26
+ # @param [Bridgetown::Rack::Roda] optional, defaults to the `RodaApp` constant
22
27
  def self.boot(roda_app = nil)
23
28
  self.loaders_manager =
24
29
  Bridgetown::Utils::LoadersManager.new(Bridgetown::Current.preloaded_configuration)
@@ -4,19 +4,29 @@ require "logger"
4
4
 
5
5
  module Bridgetown
6
6
  module Rack
7
- class Logger < Logger
7
+ class Logger < Bridgetown::LogWriter
8
8
  def self.message_with_prefix(msg)
9
- return if msg.include?("/_bridgetown/live_reload")
9
+ # return if msg.include?("/_bridgetown/live_reload")
10
10
 
11
11
  "\e[35m[Server]\e[0m #{msg}"
12
12
  end
13
13
 
14
- def initialize(*)
15
- super
14
+ def enable_prefix
16
15
  @formatter = proc do |_, _, _, msg|
17
16
  self.class.message_with_prefix(msg)
18
17
  end
19
18
  end
19
+
20
+ def add(severity, message = nil, progname = nil)
21
+ return if progname&.include?("/_bridgetown/live_reload")
22
+
23
+ super
24
+ end
25
+
26
+ def initialize(*_args)
27
+ super()
28
+ enable_prefix
29
+ end
20
30
  end
21
31
  end
22
32
  end
@@ -2,6 +2,13 @@
2
2
 
3
3
  require "rack/indifferent"
4
4
 
5
+ begin
6
+ # If it's in the Gemfile's :bridgetown_plugins group it's already been required, but we'll try
7
+ # again just to be on the safe side:
8
+ require "bridgetown-routes"
9
+ rescue LoadError
10
+ end
11
+
5
12
  class Roda
6
13
  module RodaPlugins
7
14
  module BridgetownSSR
@@ -12,6 +19,34 @@ class Roda
12
19
  end
13
20
 
14
21
  register_plugin :bridgetown_ssr, BridgetownSSR
22
+
23
+ module BridgetownBoot
24
+ module InstanceMethods
25
+ # Helper shorthand for Bridgetown::Current.site
26
+ # @return [Bridgetown::Site]
27
+ def bridgetown_site
28
+ Bridgetown::Current.site
29
+ end
30
+ end
31
+
32
+ Roda::RodaRequest.alias_method :_previous_roda_cookies, :cookies
33
+
34
+ module RequestMethods
35
+ # Monkeypatch Roda/Rack's Request object so it returns a hash which allows for
36
+ # indifferent access
37
+ def cookies
38
+ # TODO: maybe replace with a simpler hash that offers an overloaded `[]` method
39
+ _previous_roda_cookies.with_indifferent_access
40
+ end
41
+
42
+ # Starts up the Bridgetown routing system
43
+ def bridgetown
44
+ Bridgetown::Rack::Routes.start!(scope)
45
+ end
46
+ end
47
+ end
48
+
49
+ register_plugin :bridgetown_boot, BridgetownBoot
15
50
  end
16
51
  end
17
52
 
@@ -24,6 +59,7 @@ module Bridgetown
24
59
  plugin :json_parser
25
60
  plugin :cookies
26
61
  plugin :streaming
62
+ plugin :bridgetown_boot
27
63
  plugin :public, root: Bridgetown::Current.preloaded_configuration.destination
28
64
  plugin :not_found do
29
65
  output_folder = Bridgetown::Current.preloaded_configuration.destination
@@ -31,15 +67,76 @@ module Bridgetown
31
67
  rescue Errno::ENOENT
32
68
  "404 Not Found"
33
69
  end
70
+ plugin :exception_page
34
71
  plugin :error_handler do |e|
35
- puts "\n#{e.class} (#{e.message}):\n\n"
36
- puts e.backtrace
72
+ Bridgetown::Errors.print_build_error(
73
+ e, logger: Bridgetown::LogAdapter.new(self.class.opts[:common_logger])
74
+ )
75
+ next exception_page(e) if ENV.fetch("RACK_ENV", nil) == "development"
76
+
37
77
  output_folder = Bridgetown::Current.preloaded_configuration.destination
38
78
  File.read(File.join(output_folder, "500.html"))
39
79
  rescue Errno::ENOENT
40
80
  "500 Internal Server Error"
41
81
  end
42
82
 
83
+ ::Roda::RodaPlugins::ExceptionPage.class_eval do
84
+ def self.css
85
+ <<~CSS
86
+ html * { padding:0; margin:0; }
87
+ body * { padding:10px 20px; }
88
+ body * * { padding:0; }
89
+ body { font-family: -apple-system, sans-serif; font-size: 90%; }
90
+ body>div { border-bottom:1px solid #ddd; }
91
+ code { font-family: ui-monospace, monospace; }
92
+ h1 { font-weight: bold; margin-block-end: .8em; }
93
+ h2 { margin-block-end:.8em; }
94
+ h2 span { font-size:80%; color:#f7f7db; font-weight:normal; }
95
+ h3 { margin:1em 0 .5em 0; }
96
+ h4 { margin:0 0 .5em 0; font-weight: normal; }
97
+ table {
98
+ border:1px solid #ccc; border-collapse: collapse; background:white; }
99
+ tbody td, tbody th { vertical-align:top; padding:2px 3px; }
100
+ thead th {
101
+ padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
102
+ font-weight:normal; font-size:11px; border:1px solid #ddd; }
103
+ tbody th { text-align:right; opacity: 0.7; padding-right:.5em; }
104
+ table.vars { margin:5px 0 2px 40px; }
105
+ table.vars td, table.req td { font-family: ui-monospace, monospace; }
106
+ table td.code { width:100%;}
107
+ table td.code div { overflow:hidden; }
108
+ table.source th { color:#666; }
109
+ table.source td {
110
+ font-family: ui-monospace, monospace; white-space:pre; border-bottom:1px solid #eee; }
111
+ ul.traceback { list-style-type:none; }
112
+ ul.traceback li.frame { margin-bottom:1em; }
113
+ div.context { margin: 10px 0; }
114
+ div.context ol {
115
+ padding-left:30px; margin:0 10px; list-style-position: inside; }
116
+ div.context ol li {
117
+ font-family: ui-monospace, monospace; white-space:pre; color:#666; cursor:pointer; }
118
+ div.context ol.context-line li { color:black; background-color:#f7f7db; }
119
+ div.context ol.context-line li span { float: right; }
120
+ div.commands { margin-left: 40px; }
121
+ div.commands a { color:black; text-decoration:none; }
122
+ #summary { background: #1D453C; color: white; }
123
+ #summary h2 { font-weight: normal; color: white; }
124
+ #summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
125
+ #summary ul#quicklinks li { float: left; padding: 0 1em; }
126
+ #summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
127
+ #summary a { color: #f47c3c; }
128
+ #explanation { background:#eee; }
129
+ #traceback { background: white; }
130
+ #requestinfo { background:#f6f6f6; padding-left:120px; }
131
+ #summary table { border:none; background:transparent; }
132
+ #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
133
+ #requestinfo h3 { margin-bottom:-1em; }
134
+ .error { background: #ffc; }
135
+ .specific { color:#cc3300; font-weight:bold; }
136
+ CSS
137
+ end
138
+ end
139
+
43
140
  before do
44
141
  if self.class.opts[:bridgetown_site]
45
142
  # The site had previously been initialized via the bridgetown_ssr plugin
@@ -48,19 +145,14 @@ module Bridgetown
48
145
  Bridgetown::Current.preloaded_configuration ||=
49
146
  self.class.opts[:bridgetown_preloaded_config]
50
147
 
51
- request.public
52
-
53
148
  request.root do
54
149
  output_folder = Bridgetown::Current.preloaded_configuration.destination
55
150
  File.read(File.join(output_folder, "index.html"))
151
+ rescue StandardError
152
+ response.status = 500
153
+ "<p>ERROR: cannot find <code>index.html</code> in the output folder.</p>"
56
154
  end
57
155
  end
58
-
59
- # Helper shorthand for Bridgetown::Current.site
60
- # @return [Bridgetown::Site]
61
- def bridgetown_site
62
- Bridgetown::Current.site
63
- end
64
156
  end
65
157
  end
66
158
  end
@@ -9,19 +9,49 @@ module Bridgetown
9
9
  end
10
10
 
11
11
  class Routes
12
+ include Bridgetown::Prioritizable
13
+
14
+ self.priorities = {
15
+ highest: "010",
16
+ high: "020",
17
+ normal: "030",
18
+ low: "040",
19
+ lowest: "050",
20
+ }.freeze
21
+
12
22
  class << self
13
- attr_accessor :tracked_subclasses, :router_block
23
+ # @return [Hash<String, Class(Routes)>]
24
+ attr_accessor :tracked_subclasses
25
+
26
+ # @return [Proc]
27
+ attr_accessor :router_block
28
+
29
+ # Spaceship is priority [higher -> lower]
30
+ #
31
+ # @param other [Class(Routes)] The class to be compared.
32
+ # @return [Integer] -1, 0, 1.
33
+ def <=>(other)
34
+ "#{priorities[priority]}#{self}" <=> "#{priorities[other.priority]}#{other}"
35
+ end
14
36
 
37
+ # @param base [Class(Routes)]
15
38
  def inherited(base)
16
39
  Bridgetown::Rack::Routes.track_subclass base
17
40
  super
18
41
  end
19
42
 
43
+ # @param klass [Class(Routes)]
20
44
  def track_subclass(klass)
21
45
  Bridgetown::Rack::Routes.tracked_subclasses ||= {}
22
46
  Bridgetown::Rack::Routes.tracked_subclasses[klass.name] = klass
23
47
  end
24
48
 
49
+ # @return [Array<Class(Routes)>]
50
+ def sorted_subclasses
51
+ Bridgetown::Rack::Routes.tracked_subclasses&.values&.sort
52
+ end
53
+
54
+ # @return [void]
25
55
  def reload_subclasses
26
56
  Bridgetown::Rack::Routes.tracked_subclasses&.each_key do |klassname|
27
57
  Kernel.const_get(klassname)
@@ -30,33 +60,78 @@ module Bridgetown
30
60
  end
31
61
  end
32
62
 
63
+ # Add a router block via the current Routes class
64
+ #
65
+ # Example:
66
+ #
67
+ # class Routes::Hello < Bridgetown::Rack::Routes
68
+ # route do |r|
69
+ # r.get "hello", String do |name|
70
+ # { hello: "friend #{name}" }
71
+ # end
72
+ # end
73
+ # end
74
+ #
75
+ # @param block [Proc]
33
76
  def route(&block)
34
77
  self.router_block = block
35
78
  end
36
79
 
80
+ # Initialize a new Routes instance and execute the route as part of the
81
+ # Roda app request cycle
82
+ #
83
+ # @param roda_app [Bridgetown::Rack::Roda]
37
84
  def merge(roda_app)
38
85
  return unless router_block
39
86
 
40
87
  new(roda_app).handle_routes
41
88
  end
42
89
 
90
+ # Start the Roda app request cycle. There are two different code paths
91
+ # depending on if there's a site `base_path` configured
92
+ #
93
+ # @param roda_app [Bridgetown::Rack::Roda]
94
+ # @return [void]
43
95
  def start!(roda_app)
96
+ if Bridgetown::Current.preloaded_configuration.base_path == "/"
97
+ load_all_routes roda_app
98
+ return
99
+ end
100
+
101
+ # Support custom base_path configurations
102
+ roda_app.request.on(
103
+ Bridgetown::Current.preloaded_configuration.base_path.delete_prefix("/")
104
+ ) do
105
+ load_all_routes roda_app
106
+ end
107
+
108
+ nil
109
+ end
110
+
111
+ # Run the Roda public plugin first, set up live reload if allowed, then
112
+ # run through all the Routes blocks. If the file-based router plugin
113
+ # is available, kick off that request process next.
114
+ #
115
+ # @param roda_app [Bridgetown::Rack::Roda]
116
+ # @return [void]
117
+ def load_all_routes(roda_app)
118
+ roda_app.request.public
119
+
44
120
  if Bridgetown.env.development? &&
45
121
  !Bridgetown::Current.preloaded_configuration.skip_live_reload
46
122
  setup_live_reload roda_app
47
123
  end
48
124
 
49
- Bridgetown::Rack::Routes.tracked_subclasses&.each_value do |klass|
125
+ Bridgetown::Rack::Routes.sorted_subclasses&.each do |klass|
50
126
  klass.merge roda_app
51
127
  end
52
128
 
53
- if defined?(Bridgetown::Routes::RodaRouter)
54
- Bridgetown::Routes::RodaRouter.start!(roda_app)
55
- end
129
+ return unless defined?(Bridgetown::Routes::RodaRouter)
56
130
 
57
- nil
131
+ Bridgetown::Routes::RodaRouter.start!(roda_app)
58
132
  end
59
133
 
134
+ # @param app [Bridgetown::Rack::Roda]
60
135
  def setup_live_reload(app) # rubocop:disable Metrics/AbcSize
61
136
  sleep_interval = 0.2
62
137
  file_to_check = File.join(app.class.opts[:bridgetown_preloaded_config].destination,
@@ -86,14 +161,20 @@ module Bridgetown
86
161
  end
87
162
  end
88
163
 
164
+ # @param roda_app [Bridgetown::Rack::Roda]
89
165
  def initialize(roda_app)
90
166
  @_roda_app = roda_app
91
167
  end
92
168
 
169
+ # Execute the router block via the instance, passing it the Roda request
170
+ #
171
+ # @return [Object] whatever is returned by the router block as expected
172
+ # by the Roda API
93
173
  def handle_routes
94
174
  instance_exec(@_roda_app.request, &self.class.router_block)
95
175
  end
96
176
 
177
+ # Any missing methods are passed along to the underlying Roda app if possible
97
178
  def method_missing(method_name, *args, **kwargs, &block)
98
179
  if @_roda_app.respond_to?(method_name.to_sym)
99
180
  @_roda_app.send method_name.to_sym, *args, **kwargs, &block