super 0.0.6 → 0.0.11

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/CONTRIBUTING.md +56 -0
  4. data/README.md +55 -57
  5. data/Rakefile +16 -14
  6. data/STABILITY.md +50 -0
  7. data/app/assets/javascripts/super/application.js +297 -97
  8. data/app/assets/stylesheets/super/application.css +5600 -0
  9. data/app/controllers/super/application_controller.rb +15 -6
  10. data/app/helpers/super/form_builder_helper.rb +25 -0
  11. data/app/views/layouts/super/application.html.erb +22 -6
  12. data/app/views/super/application/_display_rich_text.html.erb +1 -0
  13. data/app/views/super/application/_filter.html.erb +6 -0
  14. data/app/views/super/application/_filter_type_select.html.erb +21 -0
  15. data/app/views/super/application/_filter_type_text.html.erb +18 -0
  16. data/app/views/super/application/_filter_type_timestamp.html.erb +24 -0
  17. data/app/views/super/application/_form_field__destroy.html.erb +1 -9
  18. data/app/views/super/application/_form_field_checkbox.html.erb +1 -0
  19. data/app/views/super/application/_form_field_rich_text_area.html.erb +1 -0
  20. data/app/views/super/application/_form_field_select.html.erb +1 -23
  21. data/app/views/super/application/_form_field_text.html.erb +1 -13
  22. data/app/views/super/application/_query.html.erb +18 -0
  23. data/app/views/super/application/_sort.html.erb +18 -0
  24. data/app/views/super/application/_sort_expression.html.erb +25 -0
  25. data/app/views/super/application/_super_layout.html.erb +5 -5
  26. data/app/views/super/application/_super_pagination.html.erb +16 -0
  27. data/app/views/super/application/_super_panel.html.erb +2 -2
  28. data/app/views/super/application/_super_schema_display_index.html.erb +9 -24
  29. data/app/views/super/application/_super_schema_display_show.html.erb +4 -4
  30. data/app/views/super/application/_super_schema_form.html.erb +3 -3
  31. data/app/views/super/application/edit.html.erb +2 -6
  32. data/app/views/super/application/index.html.erb +2 -6
  33. data/app/views/super/application/new.html.erb +2 -6
  34. data/app/views/super/application/show.html.erb +2 -6
  35. data/app/views/super/feather/README.md +1 -0
  36. data/app/views/super/feather/_x.html +15 -0
  37. data/config/routes.rb +2 -0
  38. data/docs/README.md +4 -2
  39. data/docs/action_text.md +48 -0
  40. data/docs/cheat.md +8 -8
  41. data/docs/faq.md +3 -3
  42. data/docs/installation.md +21 -0
  43. data/docs/quick_start.md +1 -16
  44. data/docs/webpacker.md +13 -5
  45. data/docs/yard_customizations.rb +2 -0
  46. data/frontend/super-frontend/dist/application.css +5600 -0
  47. data/frontend/super-frontend/dist/application.js +297 -97
  48. data/lib/generators/super/action_text/USAGE +23 -0
  49. data/lib/generators/super/action_text/action_text_generator.rb +32 -0
  50. data/lib/generators/super/action_text/templates/pack_super_action_text.css +23 -0
  51. data/lib/generators/super/action_text/templates/pack_super_action_text.js +4 -0
  52. data/lib/generators/super/install/install_generator.rb +2 -0
  53. data/lib/generators/super/resource/resource_generator.rb +2 -0
  54. data/lib/generators/super/resource/templates/resources_controller.rb.tt +1 -31
  55. data/lib/generators/super/webpacker/USAGE +5 -4
  56. data/lib/generators/super/webpacker/webpacker_generator.rb +5 -2
  57. data/lib/super.rb +23 -2
  58. data/lib/super/action_inquirer.rb +2 -0
  59. data/lib/super/assets.rb +114 -38
  60. data/lib/super/client_error.rb +2 -0
  61. data/lib/super/compatibility.rb +15 -1
  62. data/lib/super/configuration.rb +22 -69
  63. data/lib/super/controls.rb +6 -25
  64. data/lib/super/controls/optional.rb +71 -24
  65. data/lib/super/controls/required.rb +3 -29
  66. data/lib/super/controls/steps.rb +44 -53
  67. data/lib/super/controls/view.rb +55 -0
  68. data/lib/super/display.rb +80 -0
  69. data/lib/super/display/guesser.rb +36 -0
  70. data/lib/super/display/schema_types.rb +28 -33
  71. data/lib/super/engine.rb +11 -1
  72. data/lib/super/error.rb +14 -0
  73. data/lib/super/filter.rb +14 -0
  74. data/lib/super/filter/form_object.rb +94 -0
  75. data/lib/super/filter/guesser.rb +32 -0
  76. data/lib/super/filter/operator.rb +105 -0
  77. data/lib/super/filter/schema_types.rb +114 -0
  78. data/lib/super/form.rb +29 -40
  79. data/lib/super/form/builder.rb +206 -0
  80. data/lib/super/form/guesser.rb +29 -0
  81. data/lib/super/form/inline_errors.rb +28 -0
  82. data/lib/super/form/schema_types.rb +31 -29
  83. data/lib/super/form/strong_params.rb +31 -0
  84. data/lib/super/layout.rb +2 -0
  85. data/lib/super/link.rb +2 -0
  86. data/lib/super/navigation/automatic.rb +2 -0
  87. data/lib/super/pagination.rb +57 -0
  88. data/lib/super/panel.rb +2 -0
  89. data/lib/super/partial.rb +14 -0
  90. data/lib/super/partial/resolving.rb +2 -0
  91. data/lib/super/plugin.rb +36 -63
  92. data/lib/super/query/form_object.rb +48 -0
  93. data/lib/super/schema.rb +2 -24
  94. data/lib/super/schema/common.rb +27 -0
  95. data/lib/super/schema/guesser.rb +79 -0
  96. data/lib/super/sort.rb +110 -0
  97. data/lib/super/version.rb +3 -1
  98. data/lib/super/view_helper.rb +2 -19
  99. metadata +74 -22
  100. data/app/helpers/super/application_helper.rb +0 -39
  101. data/app/views/super/application/_form_inline_errors.html.erb +0 -10
  102. data/frontend/super-frontend/build.js +0 -36
  103. data/frontend/super-frontend/package.json +0 -21
  104. data/frontend/super-frontend/postcss.config.js +0 -6
  105. data/frontend/super-frontend/src/javascripts/super/application.ts +0 -18
  106. data/frontend/super-frontend/src/javascripts/super/apply_template_controller.ts +0 -19
  107. data/frontend/super-frontend/src/javascripts/super/rails__ujs.d.ts +0 -1
  108. data/frontend/super-frontend/src/javascripts/super/toggle_pending_destruction_controller.ts +0 -15
  109. data/frontend/super-frontend/src/stylesheets/super/application.css +0 -77
  110. data/frontend/super-frontend/tailwind.config.js +0 -15
  111. data/frontend/super-frontend/tsconfig.json +0 -13
  112. data/frontend/super-frontend/yarn.lock +0 -5448
data/lib/super/form.rb CHANGED
@@ -1,48 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Super
4
+ # This schema type is used on your +#edit+ and +#new+ forms
5
+ #
6
+ # ```ruby
7
+ # class MembersController::Controls
8
+ # # ...
9
+ #
10
+ # def new_schema
11
+ # Super::Form.new do |fields, type|
12
+ # fields[:name] = type.string
13
+ # fields[:rank] = type.select
14
+ # fields[:position] = type.string
15
+ # fields[:ship_id] = type.select(
16
+ # collection: Ship.all.map { |s| ["#{s.name} (Ship ##{s.id})", s.id] },
17
+ # )
18
+ # end
19
+ # end
20
+ #
21
+ # # ...
22
+ # end
23
+ # ```
2
24
  class Form
3
- module FieldErrorProc
4
- def error_wrapping(html_tag)
5
- if Thread.current[:super_form_builder]
6
- return html_tag
7
- end
25
+ include Schema::Common
8
26
 
9
- super
10
- end
27
+ def initialize
28
+ @fields = Schema::Fields.new
29
+ @schema_types = SchemaTypes.new(fields: @fields)
30
+ yield(@fields, @schema_types)
11
31
  end
12
32
 
13
- class Builder < ActionView::Helpers::FormBuilder
14
- # These methods were originally defined in the following files
15
- #
16
- # * actionview/lib/action_view/helpers/form_helper.rb
17
- # * actionview/lib/action_view/helpers/form_options_helper.rb
18
- # * actionview/lib/action_view/helpers/date_helper.rb
19
- %w[
20
- label text_field password_field hidden_field file_field text_area
21
- check_box radio_button color_field search_field telephone_field
22
- date_field time_field datetime_field month_field week_field url_field
23
- email_field number_field range_field
24
-
25
- select collection_select grouped_collection_select time_zone_select
26
- collection_radio_buttons collection_check_boxes
27
-
28
- time_select datetime_select date_select
29
- ].each do |field_type_method|
30
- class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
31
- def #{field_type_method}(*)
32
- Thread.current[:super_form_builder] = true
33
- super
34
- ensure
35
- Thread.current[:super_form_builder] = nil
36
- end
37
- RUBY
38
- end
39
-
40
- alias datetime_local_field datetime_field
41
- alias phone_field telephone_field
33
+ def to_partial_path
34
+ "super_schema_form"
42
35
  end
43
36
  end
44
37
  end
45
-
46
- ActionView::Helpers::Tags::Base.class_eval do
47
- prepend Super::Form::FieldErrorProc
48
- end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class Form
5
+ # Example
6
+ #
7
+ # ```ruby
8
+ # super_form_for([:admin, @member]) do |f|
9
+ # # the long way
10
+ # f.super.label :name
11
+ # f.super.text_field :name
12
+ # f.super.inline_errors :name
13
+ #
14
+ # # the short way (slightly different from the long way, for alignment)
15
+ # f.super.text_field! :position
16
+ # end
17
+ # ```
18
+ #
19
+ # Refer to the Rails docs:
20
+ # https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html
21
+ class Builder < ActionView::Helpers::FormBuilder
22
+ FIELD_ERROR_PROC = proc { |html_tag, instance| html_tag }
23
+ FORM_BUILDER_DEFAULTS = { builder: self }.freeze
24
+
25
+ def super(**options)
26
+ @super_wrappers ||= Wrappers.new(self, @template)
27
+ end
28
+
29
+ class Wrappers
30
+ def initialize(builder, template)
31
+ @builder = builder
32
+ @template = template
33
+ end
34
+
35
+ def inline_errors(attribute)
36
+ if @builder.object
37
+ messages = InlineErrors.error_messages(@builder.object, attribute).map do |msg|
38
+ error_content_tag(msg)
39
+ end
40
+
41
+ @template.safe_join(messages)
42
+ else
43
+ error_content_tag(<<~MSG.html_safe)
44
+ This form doesn't have an object, so something is probably wrong.
45
+ Maybe <code>accepts_nested_attributes_for</code> isn't set up?
46
+ MSG
47
+ end
48
+ end
49
+
50
+ def label(attribute, text = nil, options = {}, &block)
51
+ options, defaults = split_defaults(options, class: "block")
52
+ options[:class] = join_classes(defaults[:class], options[:class])
53
+
54
+ @builder.label(attribute, text, options, &block)
55
+ end
56
+
57
+ def check_box(attribute, options = {}, checked_value = "1", unchecked_value = "0")
58
+ @builder.check_box(attribute, options, checked_value, unchecked_value)
59
+ end
60
+
61
+ def password_field(attribute, options = {})
62
+ options, defaults = split_defaults(options, class: "super-input w-full")
63
+ options[:class] = join_classes(defaults[:class], options[:class])
64
+
65
+ @builder.password_field(attribute, options)
66
+ end
67
+
68
+ def rich_text_area(attribute, options = {})
69
+ options, defaults = split_defaults(options, class: "trix-content super-input w-full")
70
+ options[:class] = join_classes(defaults[:class], options[:class])
71
+
72
+ @builder.rich_text_area(attribute, options)
73
+ end
74
+
75
+ def select(attribute, choices, options = {}, html_options = {}, &block)
76
+ options, defaults = split_defaults(options, include_blank: true)
77
+ options = defaults.merge(options)
78
+ html_options, html_defaults = split_defaults(html_options, class: "super-input super-input-select-field")
79
+ html_options[:class] = join_classes(html_defaults[:class], html_options[:class])
80
+
81
+ parts = [
82
+ %(<div class="super-input-select">).html_safe,
83
+ @builder.select(attribute, choices, options, html_options, &block),
84
+ <<~HTML.html_safe,
85
+ <div class="super-input-select-icon text-blue-700">
86
+ <span class="h-4 w-4">
87
+ HTML
88
+ @template.render("super/feather/chevron_down"),
89
+ <<~HTML.html_safe,
90
+ </span>
91
+ </div>
92
+ HTML
93
+ %(</div>).html_safe,
94
+ ]
95
+
96
+ @template.safe_join(parts)
97
+ end
98
+
99
+ def submit(value = nil, options = {})
100
+ value, options = nil, value if value.is_a?(Hash)
101
+ options, defaults = split_defaults(options, class: "super-button")
102
+ options[:class] = join_classes(defaults[:class], options[:class])
103
+
104
+ @builder.submit(value, options)
105
+ end
106
+
107
+ def text_field(attribute, options = {})
108
+ options, defaults = split_defaults(options, class: "super-input w-full")
109
+ options[:class] = join_classes(defaults[:class], options[:class])
110
+
111
+ @builder.text_field(attribute, options)
112
+ end
113
+
114
+ def container(&block)
115
+ @template.content_tag(:div, class: "super-field-group", &block)
116
+ end
117
+
118
+ def check_box!(attribute, checked_value = "1", unchecked_value = "0", label: {}, field: {}, show_errors: true)
119
+ label[:super] ||= {}
120
+ label[:super] = { class: "select-none ml-1" }.merge(label[:super])
121
+ container do
122
+ compact_join([
123
+ "<div>".html_safe,
124
+ public_send(:check_box, attribute, field, checked_value, unchecked_value),
125
+ public_send(:label, attribute, nil, label),
126
+ "</div>".html_safe,
127
+ show_errors && inline_errors(attribute),
128
+ ])
129
+ end
130
+ end
131
+
132
+ def password_field!(attribute, label: {}, field: {}, show_errors: true)
133
+ container do
134
+ compact_join([
135
+ public_send(:label, attribute, label),
136
+ %(<div class="mt-1">).html_safe,
137
+ public_send(:password_field, attribute, field),
138
+ show_errors && inline_errors(attribute),
139
+ %(</div>).html_safe,
140
+ ])
141
+ end
142
+ end
143
+
144
+ def rich_text_area!(attribute, label: {}, field: {}, show_errors: true)
145
+ container do
146
+ compact_join([
147
+ public_send(:label, attribute, label),
148
+ %(<div class="mt-1">).html_safe,
149
+ public_send(:rich_text_area, attribute, field),
150
+ show_errors && inline_errors(attribute),
151
+ %(</div>).html_safe,
152
+ ])
153
+ end
154
+ end
155
+
156
+ def select!(attribute, collection, label: {}, field: {}, show_errors: true)
157
+ container do
158
+ compact_join([
159
+ public_send(:label, attribute, label),
160
+ %(<div class="mt-1">).html_safe,
161
+ public_send(:select, attribute, collection, field),
162
+ show_errors && inline_errors(attribute),
163
+ %(</div>).html_safe,
164
+ ])
165
+ end
166
+ end
167
+
168
+ def text_field!(attribute, label: {}, field: {}, show_errors: true)
169
+ container do
170
+ compact_join([
171
+ public_send(:label, attribute, label),
172
+ %(<div class="mt-1">).html_safe,
173
+ public_send(:text_field, attribute, field),
174
+ show_errors && inline_errors(attribute),
175
+ %(</div>).html_safe,
176
+ ])
177
+ end
178
+ end
179
+
180
+ private
181
+
182
+ def split_defaults(options, **internal_defaults)
183
+ defaults = options.delete(:super) || {}
184
+ # prefer options set in `defaults`, since they are user overrides
185
+ defaults = internal_defaults.merge(defaults)
186
+
187
+ [options, defaults]
188
+ end
189
+
190
+ def join_classes(*class_lists)
191
+ class_lists.flatten.map(&:presence).compact
192
+ end
193
+
194
+ def error_content_tag(content)
195
+ @template.content_tag(:p, content, class: "text-red-400 text-xs italic pt-1")
196
+ end
197
+
198
+ def compact_join(*parts)
199
+ @template.safe_join(
200
+ parts.flatten.map(&:presence).compact
201
+ )
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class Form
5
+ class Guesser
6
+ def initialize(model:, fields:, type:)
7
+ @model = model
8
+ @fields = fields
9
+ @type = type
10
+ end
11
+
12
+ def call
13
+ Schema::Guesser
14
+ .new(model: @model, fields: @fields, type: @type)
15
+ .assign_type { |attribute_name| attribute_type_for(attribute_name) }
16
+ .reject { |attribute_name| attribute_name == "id" }
17
+ .reject { |attribute_name| attribute_name == "created_at" }
18
+ .reject { |attribute_name| attribute_name == "updated_at" }
19
+ .call
20
+ end
21
+
22
+ private
23
+
24
+ def attribute_type_for(attribute_name)
25
+ @type.string
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class Form
5
+ module InlineErrors
6
+ module_function
7
+
8
+ def error_messages(model_instance, column_or_association)
9
+ errable_fields(model_instance, column_or_association)
10
+ .flat_map { |field| Compatability.errable_fields(field) }
11
+ .flat_map { |field| model_instance.errors.full_messages_for(field) }
12
+ .uniq
13
+ end
14
+
15
+ def errable_fields(model_instance, column_or_association)
16
+ column_or_association = column_or_association.to_s
17
+ reflection = model_instance.class.reflect_on_association(column_or_association)
18
+ reflection ||= model_instance.class.reflections.values.find { |r| r.foreign_key == column_or_association }
19
+
20
+ if reflection
21
+ [reflection.name.to_s, reflection.foreign_key.to_s]
22
+ else
23
+ [column_or_association]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,26 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Super
2
4
  class Form
3
- # This schema type is used on your +#edit+ and +#new+ forms
4
- #
5
- # ```ruby
6
- # class MembersController::Controls
7
- # # ...
8
- #
9
- # def new_schema
10
- # Super::Schema.new(Super::Form::SchemaTypes.new) do |fields, type|
11
- # fields[:name] = type.generic("form_field_text")
12
- # fields[:rank] = type.generic("form_field_select", collection: Member.ranks.keys)
13
- # fields[:position] = type.generic("form_field_text")
14
- # fields[:ship_id] = type.generic(
15
- # "form_field_select",
16
- # collection: Ship.all.map { |s| ["#{s.name} (Ship ##{s.id})", s.id] },
17
- # )
18
- # end
19
- # end
20
- #
21
- # # ...
22
- # end
23
- # ```
24
5
  class SchemaTypes
25
6
  class Generic
26
7
  def initialize(partial_path:, extras:, nested:)
@@ -31,6 +12,16 @@ module Super
31
12
 
32
13
  attr_reader :nested_fields
33
14
 
15
+ def each_attribute
16
+ if block_given?
17
+ @nested_fields.each do |key, value|
18
+ yield(key, value)
19
+ end
20
+ end
21
+
22
+ enum_for(:each_attribute)
23
+ end
24
+
34
25
  # This takes advantage of a feature of Rails. If the value of
35
26
  # `#to_partial_path` is `my_form_field`, Rails renders
36
27
  # `app/views/super/application/_my_form_field.html.erb`, and this
@@ -69,17 +60,32 @@ module Super
69
60
  end
70
61
  end
71
62
 
72
- def before_yield(fields:)
63
+ def initialize(fields:)
73
64
  @fields = fields
74
65
  end
75
66
 
76
- def after_yield
77
- end
78
-
79
67
  def generic(partial_path, **extras)
80
68
  Generic.new(partial_path: partial_path, extras: extras, nested: {})
81
69
  end
82
70
 
71
+ def select(**extras)
72
+ Generic.new(partial_path: "form_field_select", extras: extras, nested: {})
73
+ end
74
+
75
+ def string(**extras)
76
+ Generic.new(partial_path: "form_field_text", extras: extras, nested: {})
77
+ end
78
+
79
+ alias text string
80
+
81
+ def rich_text_area(**extras)
82
+ Generic.new(partial_path: "form_field_rich_text_area", extras: extras, nested: {})
83
+ end
84
+
85
+ def checkbox(**extras)
86
+ Generic.new(partial_path: "form_field_checkbox", extras: extras, nested: {})
87
+ end
88
+
83
89
  def has_many(reader, **extras)
84
90
  nested = @fields.nested do
85
91
  yield
@@ -113,10 +119,6 @@ module Super
113
119
  nested: {}
114
120
  )
115
121
  end
116
-
117
- def to_partial_path
118
- "super_schema_form"
119
- end
120
122
  end
121
123
  end
122
124
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class Form
5
+ class StrongParams
6
+ def initialize(form_schema)
7
+ @form_schema = form_schema
8
+ end
9
+
10
+ def require(model)
11
+ model.model_name.singular
12
+ end
13
+
14
+ def permit
15
+ unfurl(@form_schema)
16
+ end
17
+
18
+ private
19
+
20
+ def unfurl(responds_to_each_attribute)
21
+ responds_to_each_attribute.each_attribute.map do |name, type|
22
+ if type.nested_fields&.any?
23
+ { name => [:id, *unfurl(type)] }
24
+ else
25
+ name
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end