turbo_boost-commands 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
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
|
}
|