coupdoeil 1.0.3 → 1.1.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.
@@ -12,7 +12,7 @@ module Coupdoeil
12
12
  skip_around_action(*filters, raise: false)
13
13
 
14
14
  def create
15
- popover = @popover_klass.new(@popover_params, view_context)
15
+ popover = @popover_klass.new(@popover_params, self)
16
16
  render plain: popover.process(@action_name), layout: false
17
17
  end
18
18
 
@@ -35,7 +35,7 @@ module Coupdoeil
35
35
  def set_popover_params
36
36
  @popover_params =
37
37
  if params[:params].blank?
38
- @popover_params = {}.freeze
38
+ @popover_params = {}.with_indifferent_access
39
39
  else
40
40
  raw_params = JSON.parse(params[:params])
41
41
  card_params = Coupdoeil::Params.deserialize(raw_params).sole
@@ -44,3 +44,4 @@ module Coupdoeil
44
44
  end
45
45
  end
46
46
  end
47
+ ActiveSupport.run_load_hooks(:coupdoeil_popovers_controller, Coupdoeil::PopoversController)
@@ -10,7 +10,7 @@ import {cancelCloseRequest, clear as clearPopover} from "./closing"
10
10
  function fetchPopoverContent(controller) {
11
11
  const type = getType(controller)
12
12
  const params = getParams(controller)
13
- const authenticityToken = document.querySelector('meta[name=csrf-token]').content
13
+ const authenticityToken = document.querySelector('meta[name=csrf-token]')?.content
14
14
  let url = `/coupdoeil/popover`
15
15
  const opts = {
16
16
  method: 'POST',
@@ -32,17 +32,15 @@ module Coupdoeil
32
32
  OptionsSet.new(options.merge(options_set.options))
33
33
  end
34
34
 
35
- if Rails.env.local?
36
- def validate!
37
- ORDERED_OPTIONS.map do |option|
38
- next unless options.key?(option.key)
39
-
40
- value = options[option.key]
41
- option.new(value).validate!
42
- end
35
+ def validate!
36
+ return if Coupdoeil.config.validate_options == false || @to_base36.present?
37
+
38
+ ORDERED_OPTIONS.map do |option|
39
+ next unless options.key?(option.key)
40
+
41
+ value = options[option.key]
42
+ option.new(value).validate!
43
43
  end
44
- else
45
- def validate! = nil # no-op
46
44
  end
47
45
 
48
46
  def to_base36
@@ -4,22 +4,35 @@ module Coupdoeil
4
4
  class Popover < AbstractController::Base
5
5
  abstract!
6
6
 
7
- include AbstractController::Rendering
8
-
9
- include AbstractController::Logger
7
+ include AbstractController::Caching
8
+ include AbstractController::Callbacks
10
9
  include AbstractController::Helpers
10
+ include AbstractController::Logger
11
+ include AbstractController::Rendering
11
12
  include AbstractController::Translation
12
- include AbstractController::Callbacks
13
- include AbstractController::Caching
14
13
 
15
14
  include ActionView::Layouts
16
15
  include ActionView::Rendering
17
16
 
17
+ include ActionController::Cookies
18
+ include ActionController::Helpers
19
+
20
+ # For forgery protection
21
+ forgery_protection_methods = [
22
+ :form_authenticity_token,
23
+ :protect_against_forgery?,
24
+ :config,
25
+ :request_forgery_protection_token,
26
+ ]
27
+ delegate(*forgery_protection_methods, to: "context_controller.helpers")
28
+ helper_method forgery_protection_methods
29
+
18
30
  include Rails.application.routes.url_helpers
19
31
 
20
32
  layout "popover"
21
33
 
22
- # so coupdoeil helpers are always available within popovers
34
+ # so coupdoeil helpers are always available within popovers,
35
+ # even if include_all_helpers config is set to false
23
36
  helper Coupdoeil::ApplicationHelper
24
37
 
25
38
  @registry = Registry.new
@@ -91,24 +104,35 @@ module Coupdoeil
91
104
  end
92
105
  end
93
106
 
94
- attr_reader :params
107
+ attr_reader :params, :context_controller
108
+
109
+ helper_method :params
95
110
 
96
- def initialize(params, cp_view_context)
111
+ delegate :request, :session, to: :context_controller
112
+
113
+ # @param [HashWithIndifferentAccess] params the deserialized popover params that were given to `.with`
114
+ # @param [ActionController::Base] context_controller an instance of Coupdoeil::PopoversController or the current controller if the popover is rendered inline because of `loading: :preload` option.
115
+ def initialize(params, context_controller)
97
116
  super()
98
117
  @params = params
99
- @__cp_view_context = cp_view_context
118
+ @context_controller = context_controller
100
119
  end
101
120
 
102
- def helpers = @__cp_view_context
103
- def controller = @__cp_view_context.controller
104
-
105
- def view_context
106
- super.tap do |context|
107
- context.extend(ViewContextDelegation)
108
- context.popover = self
109
- context.__cp_view_context = @__cp_view_context
110
- end
121
+ _helpers_for_modification.define_method(:helpers) do
122
+ controller.context_controller.send(:helpers)
111
123
  end
124
+ _helpers_for_modification.deprecate(
125
+ helpers: "helpers are now available in popover templates without calling helpers method. \
126
+ See documentation on helpers at https://coupdoeil.org/guides/controller-api.html#helpers.",
127
+ deprecator: Coupdoeil.deprecator,
128
+ )
129
+
130
+ def controller = context_controller
131
+ deprecate(
132
+ controller: "use self instead, helper_methods are available in popovers by default. \
133
+ See documentation on helpers at https://coupdoeil.org/guides/controller-api.html#helpers.",
134
+ deprecator: Coupdoeil.deprecator,
135
+ )
112
136
 
113
137
  def render(...)
114
138
  return super unless response_body
@@ -36,7 +36,7 @@ module Coupdoeil
36
36
  attributes.merge!("popover-type" => popover_setup.identifier, "popover-params" => params)
37
37
  end
38
38
 
39
- if Rails.env.local?
39
+ if Coupdoeil.config.options_html_attributes
40
40
  attributes.merge!(popover_options.to_h.transform_keys { "popover-#{_1}" })
41
41
  end
42
42
 
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coupdoeil
4
+ class Config
5
+ class << self
6
+ def defaults
7
+ ActiveSupport::OrderedOptions.new.merge!({
8
+ validate_options: Rails.env.local?,
9
+ options_html_attributes: Rails.env.local?,
10
+ include_all_helpers: true,
11
+ delegate_helper_methods: true,
12
+ })
13
+ end
14
+
15
+ # @!attribute validate_options
16
+ # @return [Boolean]
17
+ # Whether to validate popover options when building it.
18
+ # It will raise an error with a hint on how to fix it if an option's value is not valid.
19
+ # Defaults to `Rails.env.local?`.
20
+
21
+ # @!attribute options_html_attributes
22
+ # @return [Boolean]
23
+ # Whether to insert options as HTML attributes of the <coupdoeil> tag.
24
+ # This is not required for Coupdoeil to work since options are compressed to base 36 to lighten the HTML payload.
25
+ # This option is here to help for debug or testing.
26
+ # Defaults to `Rails.env.local?`.
27
+
28
+ # @!attribute include_all_helpers
29
+ # @return [Boolean]
30
+ # Whether to load all application helpers so they are available when rendering a popover.
31
+ # This mimics the default behavior of Rails.
32
+ # It is advised to set it to true so that rendering popovers is seamless compared to rendering any other template.
33
+ # For all helpers to be included it is also required that `config.action_controller.include_all_helpers` is set to true.
34
+
35
+ # @!attribute delegate_helper_methods
36
+ # @return [Boolean]
37
+ # Similarly to `include_all_helpers`, to offer a seamless experience when rendering popovers, Coupdoeil will delegate
38
+ # any helper methods defined on ApplicationController when this option is set to true.
39
+ # Such helper methods could include `current_user` from Devise or `allowed_to?` from ActionPolicy, for example.
40
+ # As these helpers are not defined on modules like other helpers, they cannot be included and their call must
41
+ # therefore be delegated to the context controller.
42
+ # If this behavior causes unexpected behavior you can disable it with this configuration option, and/or [submit an issue](https://gitlab.com/Pagehey/coupdoeil/-/issues) so it can be investigated.
43
+ # However, in absence of problem, it is still recommended to let it `true` by default.
44
+ end
45
+ # @!attribute current
46
+ # @return [Coupdoeil::Config]
47
+ # Returns the current Coupdoeil::Config. This is persisted against this
48
+ # class so that config options remain accessible before the rest of
49
+ # Coupdoeil has loaded. Defaults to an instance of Coupdoeil::Config
50
+ # with all other documented defaults set.
51
+ class_attribute :current, default: defaults, instance_predicate: false
52
+ end
53
+ end
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "coupdoeil/config"
4
+
3
5
  module Coupdoeil
4
6
  class Engine < ::Rails::Engine
5
- isolate_namespace Coupdoeil
6
7
  config.eager_load_namespaces << Coupdoeil
7
- config.coupdoeil = ActiveSupport::OrderedOptions.new
8
+ config.coupdoeil = Coupdoeil::Config.current
8
9
 
9
10
  config.autoload_once_paths = [
10
11
  "#{root}/app/controllers",
@@ -14,8 +15,8 @@ module Coupdoeil
14
15
  "#{root}/app/models/concerns",
15
16
  ]
16
17
 
17
- # If you don't want to precompile Coupdoeil's assets (eg. because you're using webpack),
18
- # you can do this in an intiailzer:
18
+ # If you don't want to precompile Coupdoeil's assets (e.g. because you're using webpack),
19
+ # you can do this in an initializer:
19
20
  #
20
21
  # config.after_initialize do
21
22
  # config.assets.precompile -= Coupdoeil::Engine::PRECOMPILE_ASSETS
@@ -41,16 +42,22 @@ module Coupdoeil
41
42
  end
42
43
  end
43
44
 
44
- initializer "coupdoeil.logger" do
45
- stdout_logger = ActiveSupport::Logger.new($stdout)
46
- stdout_logger.formatter = ::Logger::Formatter.new
47
- tagged_stdout_logger = ActiveSupport::TaggedLogging.new(stdout_logger)
48
- config.coupdoeil.logger = tagged_stdout_logger.tagged("Coupdoeil")
45
+ initializer "coupdoeil.helpers", before: :load_config_initializers do
46
+ ActiveSupport.on_load(:coupdoeil_popovers_controller) do
47
+ next unless Coupdoeil.config.include_all_helpers && include_all_helpers
48
+
49
+ Coupdoeil::Popover.helpers_path = helpers_path
50
+ Coupdoeil::Popover.helper(:all) if include_all_helpers
51
+ end
49
52
  end
50
53
 
51
- initializer "coupdoeil.helpers", before: :load_config_initializers do
52
- ActiveSupport.on_load(:action_controller_base) do
53
- helper Coupdoeil::Engine.helpers
54
+ initializer "coupdoeil.helper_methods", before: :load_config_initializers do
55
+ ActiveSupport.on_load(:coupdoeil_popovers_controller) do
56
+ next unless Coupdoeil.config.delegate_helper_methods
57
+
58
+ helpers_to_delegate = _helper_methods - Coupdoeil::Popover._helper_methods
59
+ Coupdoeil::Popover.delegate(*helpers_to_delegate, to: "context_controller.helpers", private: true)
60
+ Coupdoeil::Popover.helper_method(helpers_to_delegate)
54
61
  end
55
62
  end
56
63
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Coupdoeil
4
- VERSION = "1.0.3"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/coupdoeil.rb CHANGED
@@ -5,4 +5,13 @@ require "coupdoeil/engine"
5
5
 
6
6
  module Coupdoeil
7
7
  extend ActiveSupport::Autoload
8
+
9
+ class << self
10
+ def config = Coupdoeil::Config.current
11
+ def configure = yield(Coupdoeil::Config.current)
12
+
13
+ def deprecator # :nodoc:
14
+ @deprecator ||= ActiveSupport::Deprecation.new("2.0.0", "Coupdoeil")
15
+ end
16
+ end
8
17
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coupdoeil
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - PageHey
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-07-15 00:00:00.000000000 Z
10
+ date: 2025-08-11 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: actionpack
@@ -178,7 +178,6 @@ files:
178
178
  - app/models/coupdoeil/popover/options_set.rb
179
179
  - app/models/coupdoeil/popover/registry.rb
180
180
  - app/models/coupdoeil/popover/setup.rb
181
- - app/models/coupdoeil/popover/view_context_delegation.rb
182
181
  - app/models/coupdoeil/tag.rb
183
182
  - app/style/popover-animation.scss
184
183
  - app/style/popover-arrow.scss
@@ -186,6 +185,7 @@ files:
186
185
  - app/views/layouts/coupdoeil/application.html.erb
187
186
  - config/routes.rb
188
187
  - lib/coupdoeil.rb
188
+ - lib/coupdoeil/config.rb
189
189
  - lib/coupdoeil/engine.rb
190
190
  - lib/coupdoeil/version.rb
191
191
  - lib/generators/coupdoeil/install/install_generator.rb
@@ -201,6 +201,9 @@ metadata:
201
201
  homepage_uri: https://coupdoeil.org
202
202
  source_code_uri: https://gitlab.com/Pagehey/coupdoeil
203
203
  changelog_uri: https://gitlab.com/Pagehey/coupdoeil/-/blob/main/CHANGELOG.md
204
+ post_install_message: |
205
+ Coupdoeil v.1.1.0 fixed the helper usage in popovers and should allow some refactoring.
206
+ See CHANGELOG for details: https://gitlab.com/Pagehey/coupdoeil/-/blob/main/CHANGELOG.md?ref_type=heads#v110
204
207
  rdoc_options: []
205
208
  require_paths:
206
209
  - lib
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coupdoeil
4
- class Popover
5
- module ViewContextDelegation
6
- attr_accessor :__cp_view_context, :popover
7
-
8
- # For CSRF authenticity tokens in forms
9
- def config = __cp_view_context.config
10
- def form_authenticity_token(...) = __cp_view_context.form_authenticity_token(...)
11
- def protect_against_forgery? = __cp_view_context.protect_against_forgery?
12
- def request_forgery_protection_token = __cp_view_context.request_forgery_protection_token
13
-
14
- def helpers = __cp_view_context
15
- def controller = __cp_view_context.controller
16
- def params = popover.params
17
- end
18
- end
19
- end