turbo_boost-commands 0.0.2 → 0.0.3
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.
Potentially problematic release.
This version of turbo_boost-commands might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile.lock +9 -11
- data/README.md +7 -6
- data/app/assets/builds/@turbo-boost/commands.js +4 -4
- data/app/assets/builds/@turbo-boost/commands.js.map +3 -3
- data/app/helpers/turbo_boost/commands/application_helper.rb +4 -0
- data/bin/standardize +1 -1
- data/lib/turbo_boost/commands/attribute_hydration.rb +95 -0
- data/lib/turbo_boost/commands/attribute_set.rb +33 -5
- data/lib/turbo_boost/commands/command.rb +19 -10
- data/lib/turbo_boost/commands/controller_pack.rb +2 -0
- data/lib/turbo_boost/commands/engine.rb +10 -1
- data/lib/turbo_boost/commands/patches/action_view_helpers_tag_helper_tag_builder_patch.rb +10 -0
- data/lib/turbo_boost/commands/patches.rb +6 -0
- data/lib/turbo_boost/commands/runner.rb +4 -3
- data/lib/turbo_boost/commands/version.rb +1 -1
- data/package.json +15 -4
- data/tags +984 -1620
- data/turbo_boost-commands.gemspec +2 -2
- data/yarn.lock +153 -153
- metadata +21 -17
data/bin/standardize
CHANGED
@@ -2,6 +2,6 @@
|
|
2
2
|
|
3
3
|
bundle exec magic_frozen_string_literal
|
4
4
|
bundle exec standardrb --fix
|
5
|
-
yarn run prettier-standard
|
5
|
+
yarn run prettier-standard package.json app/javascript/**/*.js
|
6
6
|
yarn run rustywind --write test/dummy/app
|
7
7
|
yarn run rustywind --write --custom-regex "(:\s[\"'])(.+)[\"']" test/dummy/app/views/_tailwind.yml.erb
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TurboBoost::Commands::AttributeHydration
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def hydrate(value)
|
7
|
+
case value
|
8
|
+
when Array
|
9
|
+
value.map { |val| hydrate(val) }
|
10
|
+
when Hash
|
11
|
+
value.each_with_object(HashWithIndifferentAccess.new) do |(key, val), memo|
|
12
|
+
memo[key] = hydrate(val)
|
13
|
+
end
|
14
|
+
when String
|
15
|
+
parsed_value = parse_json(value)
|
16
|
+
hydrated_value = hydrate(parsed_value) unless parsed_value.nil?
|
17
|
+
hydrated_value ||= GlobalID::Locator.locate_signed(value) if possible_sgid_string?(value)
|
18
|
+
hydrated_value || value
|
19
|
+
else
|
20
|
+
value
|
21
|
+
end
|
22
|
+
rescue => error
|
23
|
+
Rails.logger.error "Failed to hydrate value! #{value}; #{error}"
|
24
|
+
value
|
25
|
+
end
|
26
|
+
|
27
|
+
def dehydrate(value)
|
28
|
+
return value unless has_sgid?(value)
|
29
|
+
case value
|
30
|
+
when Array
|
31
|
+
value.map { |val| dehydrate val }
|
32
|
+
when Hash
|
33
|
+
value.each_with_object(HashWithIndifferentAccess.new) do |(key, val), memo|
|
34
|
+
val = dehydrate(val)
|
35
|
+
memo[key] = convert_to_json?(key, val) ? val.to_json : val
|
36
|
+
end
|
37
|
+
else
|
38
|
+
implements_sgid?(value) ? value.to_sgid_param : value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# simple regular expressions checked before attempting specific hydration strategies
|
45
|
+
PREFIX_ATTRIBUTE_REGEXP = /\Aaria|data\z/i
|
46
|
+
JSON_REGEX = /.*(\[|\{).*(\}|\]).*/
|
47
|
+
SGID_PARAM_REGEX = /.{100,}/i
|
48
|
+
|
49
|
+
# Rails implicitly converts certain keys to JSON,
|
50
|
+
# so we check keys before performing JSON conversion
|
51
|
+
def prefixed_attribute?(name)
|
52
|
+
PREFIX_ATTRIBUTE_REGEXP.match? name.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def convert_to_json?(key, value)
|
56
|
+
return false if prefixed_attribute?(key)
|
57
|
+
case value
|
58
|
+
when Array, Hash then true
|
59
|
+
else false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def possible_json_string?(value)
|
64
|
+
return false unless value.is_a?(String)
|
65
|
+
JSON_REGEX.match? value
|
66
|
+
end
|
67
|
+
|
68
|
+
def possible_sgid_string?(value)
|
69
|
+
return false unless value.is_a?(String)
|
70
|
+
SGID_PARAM_REGEX.match? value
|
71
|
+
end
|
72
|
+
|
73
|
+
def implements_sgid?(value)
|
74
|
+
value.respond_to?(:to_sgid_param) && value.try(:persisted?)
|
75
|
+
end
|
76
|
+
|
77
|
+
def find_sgid_value(value)
|
78
|
+
case value
|
79
|
+
when Array then value.find { |val| find_sgid_value val }
|
80
|
+
when Hash then find_sgid_value(value.values)
|
81
|
+
else implements_sgid?(value) ? value : nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def has_sgid?(value)
|
86
|
+
find_sgid_value(value).present?
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_json(value)
|
90
|
+
return nil unless possible_json_string?(value)
|
91
|
+
JSON.parse value
|
92
|
+
rescue
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
end
|
@@ -1,18 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "attribute_hydration"
|
4
|
+
|
3
5
|
class TurboBoost::Commands::AttributeSet
|
4
|
-
|
6
|
+
include TurboBoost::Commands::AttributeHydration
|
7
|
+
|
8
|
+
# These methods enable Ruby pattern matching
|
9
|
+
delegate :deconstruct, :deconstruct_keys, to: :to_h
|
10
|
+
|
11
|
+
def initialize(attributes = {}, prefix: nil)
|
5
12
|
prefix = prefix.to_s
|
6
|
-
attrs = attributes
|
13
|
+
attrs = hydrate(attributes)
|
7
14
|
|
8
15
|
attrs.each do |key, value|
|
9
16
|
key = key.to_s.strip
|
10
17
|
|
11
|
-
next unless key.start_with?(prefix)
|
18
|
+
next unless prefix.blank? || key.start_with?(prefix)
|
12
19
|
|
13
|
-
name = key.parameterize.underscore
|
14
|
-
|
20
|
+
name = key.parameterize.underscore
|
21
|
+
name.delete_prefix!("#{prefix}_") unless prefix.blank?
|
22
|
+
|
23
|
+
# type casting
|
24
|
+
value = value.to_i if value.is_a?(String) && value.match?(/\A\d+\z/)
|
15
25
|
value = value == "true" if value.is_a?(String) && value.match?(/\A(true|false)\z/i)
|
26
|
+
|
16
27
|
instance_variable_set "@#{name}", value
|
17
28
|
|
18
29
|
next if orig_respond_to_missing?(name, false)
|
@@ -22,6 +33,23 @@ class TurboBoost::Commands::AttributeSet
|
|
22
33
|
end
|
23
34
|
end
|
24
35
|
|
36
|
+
def to_h
|
37
|
+
instance_variables.each_with_object({}.with_indifferent_access) do |name, memo|
|
38
|
+
value = instance_variable_get(name)
|
39
|
+
value = value.to_h if value.is_a?(self.class)
|
40
|
+
memo[name.to_s.delete_prefix("@").to_sym] = value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def render_options
|
45
|
+
options = {
|
46
|
+
partial: renders,
|
47
|
+
assigns: assigns,
|
48
|
+
locals: locals
|
49
|
+
}
|
50
|
+
options.deep_symbolize_keys
|
51
|
+
end
|
52
|
+
|
25
53
|
def respond_to?(name, include_all = false)
|
26
54
|
respond_to_missing? name, include_all
|
27
55
|
end
|
@@ -8,18 +8,19 @@ require_relative "attribute_set"
|
|
8
8
|
# Commands are executed via a before_action in the Rails controller lifecycle.
|
9
9
|
# They have access to the following methods and properties.
|
10
10
|
#
|
11
|
+
# * controller .................. The Rails controller processing the HTTP request
|
12
|
+
# * css_id_selector ............. Returns a CSS selector for an element `id` i.e. prefixes with `#`
|
11
13
|
# * dom_id ...................... The Rails dom_id helper
|
12
14
|
# * dom_id_selector ............. Returns a CSS selector for a dom_id
|
13
|
-
# * controller .................. The Rails controller processing the HTTP request
|
14
15
|
# * element ..................... A struct that represents the DOM element that triggered the command
|
15
16
|
# * morph ....................... Appends a Turbo Stream to morph a DOM element
|
16
17
|
# * params ...................... Commands specific params (frame_id, element, etc.)
|
17
18
|
# * render ...................... Renders Rails templates, partials, etc. (doesn't halt controller request handling)
|
18
19
|
# * render_response ............. Renders a full controller response
|
19
20
|
# * renderer .................... An ActionController::Renderer
|
21
|
+
# * state ....................... An object that stores ephemeral `state`
|
20
22
|
# * turbo_stream ................ A Turbo Stream TagBuilder
|
21
23
|
# * turbo_streams ............... A list of Turbo Streams to append to the response (also aliased as streams)
|
22
|
-
# * state ....................... An object that stores ephemeral `state`
|
23
24
|
#
|
24
25
|
# They also have access to the following class methods:
|
25
26
|
#
|
@@ -27,6 +28,11 @@ require_relative "attribute_set"
|
|
27
28
|
#
|
28
29
|
class TurboBoost::Commands::Command
|
29
30
|
class << self
|
31
|
+
def css_id_selector(id)
|
32
|
+
return id if id.to_s.start_with?("#")
|
33
|
+
"##{id}"
|
34
|
+
end
|
35
|
+
|
30
36
|
def preventers
|
31
37
|
@preventers ||= Set.new
|
32
38
|
end
|
@@ -70,6 +76,7 @@ class TurboBoost::Commands::Command
|
|
70
76
|
attr_reader :controller, :turbo_streams
|
71
77
|
alias_method :streams, :turbo_streams
|
72
78
|
|
79
|
+
delegate :css_id_selector, to: :"self.class"
|
73
80
|
delegate :dom_id, to: :"controller.view_context"
|
74
81
|
delegate(
|
75
82
|
:controller_action_prevented?,
|
@@ -86,7 +93,7 @@ class TurboBoost::Commands::Command
|
|
86
93
|
end
|
87
94
|
|
88
95
|
def dom_id_selector(...)
|
89
|
-
|
96
|
+
css_id_selector dom_id(...)
|
90
97
|
end
|
91
98
|
|
92
99
|
# Same method signature as ActionView::Rendering#render (i.e. controller.view_context.render)
|
@@ -105,12 +112,14 @@ class TurboBoost::Commands::Command
|
|
105
112
|
ivars&.each { |key, value| controller.instance_variable_set "@#{key}", value }
|
106
113
|
end
|
107
114
|
|
108
|
-
def morph(
|
115
|
+
def morph(html:, id: nil, selector: nil)
|
116
|
+
selector ||= css_id_selector(id)
|
109
117
|
turbo_streams << turbo_stream.invoke("morph", args: [html], selector: selector)
|
110
118
|
end
|
111
119
|
|
112
120
|
# default command invoked when method not specified
|
113
|
-
|
121
|
+
# can be overridden in subclassed commands
|
122
|
+
def perform
|
114
123
|
end
|
115
124
|
|
116
125
|
def params
|
@@ -119,11 +128,11 @@ class TurboBoost::Commands::Command
|
|
119
128
|
|
120
129
|
def element
|
121
130
|
@element ||= begin
|
122
|
-
attributes = params[:element_attributes]
|
123
|
-
|
124
|
-
aria: TurboBoost::Commands::AttributeSet.new(
|
125
|
-
|
126
|
-
)
|
131
|
+
attributes = params[:element_attributes].to_h
|
132
|
+
TurboBoost::Commands::AttributeSet.new(attributes.merge(
|
133
|
+
aria: TurboBoost::Commands::AttributeSet.new(attributes, prefix: "aria"),
|
134
|
+
data: TurboBoost::Commands::AttributeSet.new(attributes, prefix: "data")
|
135
|
+
))
|
127
136
|
end
|
128
137
|
end
|
129
138
|
|
@@ -3,9 +3,11 @@
|
|
3
3
|
require "turbo-rails"
|
4
4
|
require "turbo_boost/streams"
|
5
5
|
require_relative "version"
|
6
|
+
require_relative "patches"
|
6
7
|
require_relative "command"
|
7
8
|
require_relative "controller_pack"
|
8
9
|
require_relative "../../../app/controllers/concerns/turbo_boost/commands/controller"
|
10
|
+
require_relative "../../../app/helpers/turbo_boost/commands/application_helper"
|
9
11
|
|
10
12
|
module TurboBoost::Commands
|
11
13
|
def self.config
|
@@ -24,8 +26,15 @@ module TurboBoost::Commands
|
|
24
26
|
initializer "turbo_boost_commands.configuration" do
|
25
27
|
Mime::Type.register "text/vnd.turbo-boost.html", :turbo_boost
|
26
28
|
|
27
|
-
ActiveSupport.on_load
|
29
|
+
ActiveSupport.on_load :action_controller_base do
|
30
|
+
# `self` is ActionController::Base
|
28
31
|
include TurboBoost::Commands::Controller
|
32
|
+
helper TurboBoost::Commands::ApplicationHelper
|
33
|
+
end
|
34
|
+
|
35
|
+
ActiveSupport.on_load :action_view do
|
36
|
+
# `self` is ActionView::Base
|
37
|
+
ActionView::Helpers::TagHelper::TagBuilder.prepend TurboBoost::Commands::Patches::ActionViewHelpersTagHelperTagBuilderPatch
|
29
38
|
end
|
30
39
|
end
|
31
40
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../attribute_hydration"
|
4
|
+
|
5
|
+
module TurboBoost::Commands::Patches::ActionViewHelpersTagHelperTagBuilderPatch
|
6
|
+
def tag_options(options, ...)
|
7
|
+
dehydrated_options = TurboBoost::Commands::AttributeHydration.dehydrate(options)
|
8
|
+
super(dehydrated_options, ...)
|
9
|
+
end
|
10
|
+
end
|
@@ -72,18 +72,19 @@ class TurboBoost::Commands::Runner
|
|
72
72
|
|
73
73
|
def command_name
|
74
74
|
return nil unless command_requested?
|
75
|
-
# binding.pry
|
76
75
|
command_params[:name]
|
77
76
|
end
|
78
77
|
|
79
78
|
def command_class_name
|
80
79
|
return nil unless command_requested?
|
81
|
-
command_name.split("#").first
|
80
|
+
name = command_name.split("#").first
|
81
|
+
name << "Command" unless name.end_with?("Command")
|
82
|
+
name
|
82
83
|
end
|
83
84
|
|
84
85
|
def command_method_name
|
85
86
|
return nil unless command_requested?
|
86
|
-
return "
|
87
|
+
return "perform" unless command_name.include?("#")
|
87
88
|
command_name.split("#").last
|
88
89
|
end
|
89
90
|
|
data/package.json
CHANGED
@@ -1,13 +1,24 @@
|
|
1
1
|
{
|
2
2
|
"name": "@turbo-boost/commands",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.2",
|
4
4
|
"description": "Commands to help you build robust reactive applications with Rails & Hotwire.",
|
5
|
-
"
|
5
|
+
"keywords": [
|
6
|
+
"hotwire",
|
7
|
+
"hotwired",
|
8
|
+
"rails",
|
9
|
+
"turbo",
|
10
|
+
"turbo-boost"
|
11
|
+
],
|
12
|
+
"type": "module",
|
13
|
+
"main": "app/assets/builds/@turbo-boost/commands.js",
|
14
|
+
"files": [
|
15
|
+
"app/assets/builds"
|
16
|
+
],
|
6
17
|
"repository": "https://github.com/hopsoft/turbo_boost-commands",
|
7
18
|
"author": "Nate Hopkins (hopsoft) <natehop@gmail.com>",
|
8
19
|
"license": "MIT",
|
9
20
|
"dependencies": {
|
10
|
-
"@turbo-boost/streams": ">= 0.0.
|
21
|
+
"@turbo-boost/streams": ">= 0.0.4"
|
11
22
|
},
|
12
23
|
"peerDependencies": {
|
13
24
|
"@hotwired/turbo-rails": ">= 7.2.0"
|
@@ -20,6 +31,6 @@
|
|
20
31
|
"rustywind": "^0.15.1"
|
21
32
|
},
|
22
33
|
"scripts": {
|
23
|
-
"build": "esbuild app/javascript/index.js --bundle --minify --sourcemap --format=esm --target=
|
34
|
+
"build": "esbuild app/javascript/index.js --bundle --minify --sourcemap --format=esm --target=es2020,chrome58,firefox57,safari11 --analyze --outfile=app/assets/builds/@turbo-boost/commands.js"
|
24
35
|
}
|
25
36
|
}
|