active_scaffold 4.3.2 → 4.4.1

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.rdoc +26 -0
  3. data/README.md +4 -6
  4. data/app/assets/javascripts/active_scaffold/active_scaffold.js +104 -12
  5. data/app/assets/javascripts/active_scaffold/draggable_lists.js +4 -3
  6. data/app/assets/javascripts/active_scaffold/load.js +20 -5
  7. data/app/assets/stylesheets/active_scaffold/_images.scss +53 -1
  8. data/app/assets/stylesheets/active_scaffold/_layout.scss +1 -66
  9. data/app/views/active_scaffold_overrides/_add_existing_form.html.erb +17 -11
  10. data/app/views/active_scaffold_overrides/_base_form.html.erb +29 -19
  11. data/app/views/active_scaffold_overrides/_field_search.html.erb +9 -9
  12. data/app/views/active_scaffold_overrides/_field_search_columns.html.erb +29 -33
  13. data/app/views/active_scaffold_overrides/_form.html.erb +43 -34
  14. data/app/views/active_scaffold_overrides/_form_association.html.erb +3 -3
  15. data/app/views/active_scaffold_overrides/_form_association_footer.html.erb +20 -16
  16. data/app/views/active_scaffold_overrides/_form_association_record.html.erb +3 -4
  17. data/app/views/active_scaffold_overrides/_horizontal_subform_header.html.erb +2 -1
  18. data/app/views/active_scaffold_overrides/_list.html.erb +6 -4
  19. data/app/views/active_scaffold_overrides/_list_calculations.html.erb +1 -1
  20. data/app/views/active_scaffold_overrides/_list_header.html.erb +3 -3
  21. data/app/views/active_scaffold_overrides/_list_messages.html.erb +19 -19
  22. data/app/views/active_scaffold_overrides/_list_messages_content.html.erb +5 -5
  23. data/app/views/active_scaffold_overrides/_list_pagination.html.erb +7 -5
  24. data/app/views/active_scaffold_overrides/_list_pagination_links.html.erb +5 -3
  25. data/app/views/active_scaffold_overrides/_list_record.html.erb +13 -8
  26. data/app/views/active_scaffold_overrides/_list_with_header.html.erb +8 -8
  27. data/app/views/active_scaffold_overrides/_messages.html.erb +3 -3
  28. data/app/views/active_scaffold_overrides/_render_field.js.erb +8 -11
  29. data/app/views/active_scaffold_overrides/_search.html.erb +7 -6
  30. data/app/views/active_scaffold_overrides/_show.html.erb +4 -4
  31. data/app/views/active_scaffold_overrides/_show_actions.html.erb +2 -2
  32. data/app/views/active_scaffold_overrides/_show_columns.html.erb +28 -30
  33. data/app/views/active_scaffold_overrides/add_existing.js.erb +7 -4
  34. data/app/views/active_scaffold_overrides/render_field.js.erb +1 -1
  35. data/config/locales/de.yml +2 -1
  36. data/config/locales/en.yml +33 -32
  37. data/config/locales/es.yml +15 -14
  38. data/config/locales/fr.yml +3 -2
  39. data/config/locales/hu.yml +3 -2
  40. data/config/locales/ja.yml +2 -1
  41. data/config/locales/ro.yml +169 -0
  42. data/config/locales/ru.yml +8 -7
  43. data/config/locales/ta.yml +7 -6
  44. data/config/locales/zh_Hans.yml +14 -13
  45. data/lib/active_scaffold/actions/common_search.rb +1 -14
  46. data/lib/active_scaffold/actions/core.rb +55 -16
  47. data/lib/active_scaffold/actions/field_search.rb +2 -2
  48. data/lib/active_scaffold/actions/list.rb +35 -13
  49. data/lib/active_scaffold/actions/nested.rb +31 -7
  50. data/lib/active_scaffold/actions/show.rb +1 -1
  51. data/lib/active_scaffold/active_record_permissions.rb +1 -1
  52. data/lib/active_scaffold/attribute_params.rb +2 -1
  53. data/lib/active_scaffold/bridges/date_picker/helper.rb +14 -0
  54. data/lib/active_scaffold/bridges/date_picker.rb +10 -0
  55. data/lib/active_scaffold/config/list.rb +10 -1
  56. data/lib/active_scaffold/core.rb +9 -6
  57. data/lib/active_scaffold/data_structures/action_columns.rb +2 -0
  58. data/lib/active_scaffold/data_structures/column.rb +27 -6
  59. data/lib/active_scaffold/data_structures/filter.rb +4 -0
  60. data/lib/active_scaffold/data_structures/nested_info.rb +8 -1
  61. data/lib/active_scaffold/data_structures/sorting.rb +1 -1
  62. data/lib/active_scaffold/engine.rb +6 -0
  63. data/lib/active_scaffold/extensions/action_view_rendering.rb +1 -1
  64. data/lib/active_scaffold/extensions/active_record_session_store.rb +49 -0
  65. data/lib/active_scaffold/finder.rb +73 -21
  66. data/lib/active_scaffold/helpers/action_link_helpers.rb +43 -18
  67. data/lib/active_scaffold/helpers/assets_helpers.rb +4 -4
  68. data/lib/active_scaffold/helpers/filter_helpers.rb +3 -3
  69. data/lib/active_scaffold/helpers/form_column_helpers.rb +34 -22
  70. data/lib/active_scaffold/helpers/form_ui_helpers.rb +15 -10
  71. data/lib/active_scaffold/helpers/framework_ui_helpers.rb +22 -0
  72. data/lib/active_scaffold/helpers/human_condition_helpers.rb +1 -0
  73. data/lib/active_scaffold/helpers/list_column_helpers.rb +19 -0
  74. data/lib/active_scaffold/helpers/pagination_helpers.rb +28 -7
  75. data/lib/active_scaffold/helpers/search_column_helpers.rb +32 -5
  76. data/lib/active_scaffold/helpers/tabs_helpers.rb +7 -7
  77. data/lib/active_scaffold/helpers/view_helpers.rb +12 -0
  78. data/lib/active_scaffold/tableless.rb +11 -8
  79. data/lib/active_scaffold/version.rb +2 -2
  80. data/lib/active_scaffold.rb +169 -0
  81. data/lib/generators/active_scaffold/install/install_generator.rb +216 -111
  82. data/lib/tasks/bundle.rake +2 -1
  83. data/lib/tasks/mock_app.rake +43 -0
  84. metadata +21 -7
  85. data/shoulda_macros/macros.rb +0 -145
@@ -58,6 +58,174 @@ module ActiveScaffold
58
58
  mattr_accessor :javascripts, instance_writer: false
59
59
  self.javascripts = []
60
60
 
61
+ # A way to define the tags and html attributes used in different places.
62
+ # Changing the class or replacing the attributes may break the default AS stylesheets, but not JS or functionality.
63
+ #
64
+ # The keys in the hash are the names used by ActiveScaffold to identify the
65
+ # elements.
66
+ # The values are a hash of attributes used to create the markup, supporting the following keys:
67
+ # * The :tag key can be used to specify a different tag name for the element.
68
+ # * The :attributes key can be used to specify additional html attributes.
69
+ # * The :proc key can be used to specify a method to dinamically return the tag and attributes hash, as an array.
70
+ # When :proc is present, :tag and :attributes won't be used. The proc will receive an options hash, which usually
71
+ # will be a hash with html attributes, but in some cases may have other options:
72
+ # * For :action_link_group the hash will have keys such as :level, :first_action, and other options supported by
73
+ # +display_action_links+
74
+ # * For :action_link_separator the hash will have keys such as :level, :level_0_tag, and other options supported by
75
+ # +display_action_links+
76
+ # * Form and search elements receive the column and the configured UI in the options hash.
77
+ # * Action link elements receive the link, record, rendering options, and authorized state.
78
+ # * Pagination elements receive the page number and, for links, the optional text key.
79
+ # Some keys don't have a :tag key, because only attributes are used, the tag is hardcoded, usually elements that are
80
+ # rendered as a form or table, or a table's element (thead, tbody, tr, td, th, tfoot, etc.). For those elements,
81
+ # the :tag key will be ignored if it's set, so the tag can't be changed.
82
+ mattr_reader :ui_elements
83
+ @@ui_elements ||= {
84
+ list: {tag: :div},
85
+ list_header: {tag: :div},
86
+ list_title: {tag: :h2},
87
+ list_actions: {tag: :div},
88
+ filters: {tag: :div, attributes: {class: 'filters'}},
89
+ before_header_table: {tag: :table},
90
+ list_content: {tag: :div},
91
+ list_table: {}, # table
92
+ list_table_head: {}, # thead
93
+ list_table_head_row: {}, # tr
94
+ list_records: {}, # tbody
95
+ list_messages: {attributes: {class: 'messages'}}, # tbody
96
+ list_messages_container: {}, # td
97
+ list_action_messages: {tag: :div},
98
+ filtered_message: {tag: :div, attributes: {class: 'filtered-message'}},
99
+ applied_filters_message: {tag: :div, attributes: {class: 'filtered-message applied-filters'}},
100
+ empty_message: {tag: :p},
101
+ message_reset: {tag: :div, attributes: {class: 'reset'}},
102
+ message_close: {tag: :a, attributes: {href: '#', class: 'close'}},
103
+ server_error: {tag: :p, attributes: {class: 'error-message message server-error'}},
104
+ info_message: {tag: :div, attributes: {class: 'info-message message'}},
105
+ warning_message: {tag: :div, attributes: {class: 'warning-message message'}},
106
+ error_message: {tag: :div, attributes: {class: 'error-message message'}},
107
+ list_footer: {tag: :div, attributes: {class: 'active-scaffold-footer'}},
108
+ list_found: {tag: :div},
109
+ list_found_count: {tag: :span},
110
+ list_calculations: {}, # tr
111
+ pagination_links: {tag: :div},
112
+ pagination_link: {}, # a
113
+ pagination_active_page: {tag: :span},
114
+ record_actions_cell: {}, # td
115
+ record_action_links: {}, # table
116
+ action_link_group: {
117
+ proc: lambda do |options|
118
+ if options[:level]&.zero?
119
+ tag = :div
120
+ attributes = {class: 'action_group'}
121
+ else
122
+ tag = :li
123
+ attributes = {class: ('top' if options[:first_action]).to_s}
124
+ end
125
+ attributes[:class] += ' hover_click' if hover_via_click?
126
+ [tag, attributes]
127
+ end
128
+ },
129
+ action_link_separator: {
130
+ proc: lambda do |options|
131
+ tag = options[:level_0_tag] || :a if options[:level]&.zero?
132
+ [tag || :li, {class: 'separator'}]
133
+ end
134
+ },
135
+ action_link_group_title: {tag: :div},
136
+ action_link_group_content: {tag: :ul},
137
+ action_link: {}, # a
138
+ action_link_item: {tag: :li},
139
+ dynamic_action_group: {tag: :ul, attributes: {class: 'dynamic-menu'}},
140
+ dynamic_action_group_element: {tag: :li},
141
+ search_form: {}, # form
142
+ search_field: {attributes: {class: 'text-input', size: 50, autocomplete: :off}}, # input type=search
143
+ search_control: {}, # input, select or textarea
144
+ search_submit: {attributes: {class: 'submit'}}, # input type=submit
145
+ search_reset: {attributes: {class: 'reset'}}, # a
146
+ field_search_form: {}, # form
147
+ field_search_fields_columns: {tag: :div, attributes: {class: 'form-columns'}},
148
+ field_search_fields_container: {tag: :ol},
149
+ field_search_subsection: {tag: :li, attributes: {class: 'sub-section'}},
150
+ field_search_element: {tag: :li},
151
+ field_search_submit: {attributes: {class: 'submit'}}, # input type=submit
152
+ field_search_reset: {attributes: {class: 'reset'}}, # a
153
+ field_search_footer: {tag: :p},
154
+ form: {}, # form
155
+ form_title: {tag: :h4},
156
+ form_messages_container: {tag: :div},
157
+ form_fields_columns: {tag: :div, attributes: {class: 'form-columns'}},
158
+ fields_container: {tag: :ol},
159
+ form_subsection: {tag: :li, attributes: {class: 'sub-section'}},
160
+ form_subsection_header: {tag: :h5},
161
+ subform: {tag: :li},
162
+ subform_header: {tag: :h5},
163
+ subform_footer: {tag: :div, attributes: {class: 'footer'}},
164
+ subform_record_remove: {}, # a
165
+ subform_record_remove_reason: {tag: :span, attributes: {class: 'destroy reason'}},
166
+ subform_create_another: {}, # a
167
+ subform_replace_with_new: {}, # a
168
+ subform_add_existing: {}, # a
169
+ form_element: {tag: :li},
170
+ form_attribute: {tag: :dl},
171
+ form_attribute_label: {tag: :dt},
172
+ form_attribute_field: {tag: :dd},
173
+ search_attribute: {tag: :dl},
174
+ search_attribute_label: {tag: :dt},
175
+ search_attribute_field: {tag: :dd},
176
+ form_control: {}, # input, select or textarea
177
+ form_field_description: {tag: :span, attributes: {class: 'description'}},
178
+ form_field_description_close: {tag: :span, attributes: {class: 'close'}},
179
+ form_footer: {tag: :p},
180
+ form_submit: {attributes: {class: 'submit'}}, # input type=submit
181
+ form_apply: {attributes: {class: 'submit'}}, # input type=submit
182
+ form_cancel: {}, # a
183
+ field_add_new_subform: {},
184
+ field_add_new_popup: {},
185
+ show_title: {tag: :h4},
186
+ show_actions: {tag: :div},
187
+ show_columns_block: {tag: :dl},
188
+ show_label_block: {tag: :dt},
189
+ show_columns_group: {tag: :dd},
190
+ show_value_block: {tag: :dd},
191
+ show_footer: {tag: :p, attributes: {class: 'form-footer'}},
192
+ show_cancel: {} # a
193
+ }
194
+
195
+ def self.set_element_tag(name, tag)
196
+ (ui_elements[name] ||= {})[:tag] = tag
197
+ end
198
+
199
+ # Replace an element's complete definition. Use a block instead of +tag+ and
200
+ # +attributes+ to define a dynamic element.
201
+ def self.set_element(name, tag: nil, attributes: nil, &proc)
202
+ if proc && (tag || attributes)
203
+ raise ArgumentError, 'tag and attributes cannot be used with a proc'
204
+ end
205
+
206
+ ui_elements[name] = if proc
207
+ {proc: proc}
208
+ else
209
+ {}.tap do |element|
210
+ element[:tag] = tag if tag
211
+ element[:attributes] = attributes.as_html_attrs if attributes
212
+ end
213
+ end
214
+ end
215
+
216
+ def self.set_element_proc(name, &block)
217
+ (ui_elements[name] ||= {})[:proc] = block
218
+ end
219
+
220
+ def self.set_element_attributes(name, attributes)
221
+ (ui_elements[name] ||= {})[:attributes] = attributes.as_html_attrs
222
+ end
223
+
224
+ def self.add_element_attributes(name, attributes)
225
+ attrs = (ui_elements[name] ||= {})[:attributes] || {}
226
+ ui_elements[name][:attributes] = attrs.smart_merge(attributes)
227
+ end
228
+
61
229
  def self.threadsafe!; end
62
230
 
63
231
  mattr_writer :jquery_ui_loaded, instance_writer: false
@@ -106,3 +274,4 @@ require 'active_scaffold/engine'
106
274
  require 'ice_nine'
107
275
  require 'ice_nine/core_ext/object'
108
276
  require 'request_store'
277
+ require 'html_attrs'
@@ -1,11 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rails/generators/base'
4
- # require 'generators/active_scaffold_controller/active_scaffold_controller_generator'
5
4
 
6
5
  module ActiveScaffold
7
6
  module Generators
8
7
  class InstallGenerator < Rails::Generators::Base
8
+ IMPORTMAP = 'config/importmap.rb'
9
+ JS_ASSET = 'app/assets/javascripts/application.js'
10
+ JS_APP = 'app/javascript/application.js'
11
+ LAYOUT = 'app/views/layouts/application.html.erb'
12
+ MANIFEST = 'app/assets/config/manifest.js'
13
+ SCSS_ASSET = 'app/assets/stylesheets/application.scss'
14
+ CSS_ASSETS = %w[app/assets/stylesheets/application.css app/assets/stylesheets/application.css.erb].freeze
15
+
9
16
  def self.base_root
10
17
  File.expand_path '../..', __dir__
11
18
  end
@@ -15,151 +22,249 @@ module ActiveScaffold
15
22
  route 'concern :active_scaffold_association, ActiveScaffold::Routing::Association.new'
16
23
  end
17
24
 
18
- IMPORTMAP = 'config/importmap.rb'
19
- JS_ASSET = 'app/assets/javascripts/application.js'
20
- JS_APP = 'app/javascript/application.js'
21
- MANIFEST = 'app/assets/config/manifest.js'
22
-
23
25
  def add_javascript
24
26
  if File.exist?(IMPORTMAP)
25
- add_to_importmap
26
- add_to_js_app
27
- elsif File.exist?(JS_ASSET) # rails 6.1
28
- original_js = File.binread(JS_ASSET)
29
- if original_js.include?('require active_scaffold')
30
- say_status('skipped', "insert into #{JS_ASSET}", :yellow)
31
- else
32
- insert_into_file JS_ASSET, after: %r{//= require +.*ujs['"]?\n} do
33
- "//= require active_scaffold\n"
34
- end
35
- add_to_manifest 'active_scaffold/manifest.js'
36
- end
37
- setup_jquery JS_ASSET, original_js
27
+ add_importmap_javascript
28
+ elsif use_sprockets_javascript?
29
+ add_sprockets_javascript
30
+ elsif propshaft? || File.exist?(JS_APP)
31
+ add_propshaft_javascript_tags
38
32
  else
39
- create_javascript_manifest JS_ASSET
33
+ raise Thor::Error, 'Unable to configure ActiveScaffold JavaScript: no import map, Propshaft setup, ' \
34
+ "#{JS_APP}, or #{JS_ASSET} was found"
40
35
  end
41
36
  end
42
37
 
43
- def add_to_stylesheet_manifest
44
- file = 'app/assets/stylesheets/application.css'
45
- return unless File.exist?(file)
46
-
47
- original_css = File.binread(file)
48
- if original_css.match?(/require active_scaffold$/)
49
- say_status('skipped', 'insert into app/assets/stylesheets/application.css', :yellow)
38
+ def add_stylesheet
39
+ if File.exist?(SCSS_ASSET)
40
+ add_to_scss_asset
50
41
  else
51
- insert_into_file 'app/assets/stylesheets/application.css', before: %r{ *\*/} do
52
- " *= require active_scaffold\n"
53
- end
42
+ add_to_css_asset
54
43
  end
55
44
  end
56
45
 
57
46
  protected
58
47
 
48
+ def add_importmap_javascript
49
+ unless File.exist?(JS_APP)
50
+ raise Thor::Error, "Unable to configure import maps: #{JS_APP} does not exist"
51
+ end
52
+
53
+ add_to_importmap
54
+ add_to_js_app
55
+ add_importmap_layout_tags if propshaft?
56
+ end
57
+
59
58
  def add_to_importmap
60
59
  original_js = File.binread(IMPORTMAP)
61
- if original_js.match?(/^pin +['"]active_scaffold['"](?=,|$)/)
60
+ pin = propshaft? ? "pin 'active_scaffold', to: 'active_scaffold/load.js'" : "pin 'active_scaffold'"
61
+ current_pin = original_js[/^pin +['"]active_scaffold['"].*$/, 0]
62
+ if current_pin == pin
62
63
  say_status('skipped', "append active_scaffold to #{IMPORTMAP}", :yellow)
64
+ elsif current_pin && generated_active_scaffold_pin?(current_pin)
65
+ gsub_file IMPORTMAP, current_pin, pin
63
66
  else
64
- append_to_file IMPORTMAP do
65
- "pin 'active_scaffold'\n"
66
- end
67
+ append_to_file IMPORTMAP, "#{pin}\n"
67
68
  end
68
- if Object.const_defined?(:Jquery)
69
- if original_js.match?(/^pin +['"]jquery['"](?=,|$)/)
70
- say_status('skipped', "append jquery to #{IMPORTMAP}", :yellow)
71
- else
72
- append_to_file IMPORTMAP do
73
- "pin 'jquery'\n"
74
- end
75
- end
76
- if original_js.match?(%r{^pin +['"](jquery_ujs|@rails/ujs)['"](?=,|$)})
77
- say_status('skipped', "append jquery_ujs to #{IMPORTMAP}", :yellow)
78
- else
79
- append_to_file IMPORTMAP do
80
- "pin 'jquery_ujs'\n"
81
- end
82
- end
69
+
70
+ if jquery?
71
+ add_importmap_pin original_js, 'jquery'
72
+ add_importmap_pin original_js, 'jquery_ujs', alternatives: ['@rails/ujs']
83
73
  else
84
- say_status('missing', 'no jquery-rails gem, load jquery, and jquery_ujs or @rails/ujs, in your layout, or add jquery-rails ' \
85
- "to Gemfile and add pin \"jquery\" and pin \"jquery_ujs\" or pin \"@rails/ujs\", to #{IMPORTMAP}", :red)
74
+ say_status('missing', 'no jquery-rails gem; load jquery and jquery_ujs or @rails/ujs manually', :red)
86
75
  end
87
76
  end
88
77
 
89
- def add_to_js_app
90
- original_js = File.binread(JS_APP)
91
- if original_js.match?(/^import +['"]active_scaffold['"]/)
92
- say_status('skipped', "append active_scaffold to #{JS_APP}", :yellow)
78
+ def add_importmap_pin(original_js, name, alternatives: [])
79
+ names = [name, *alternatives].map { |item| Regexp.escape(item) }.join('|')
80
+ if original_js.match?(/^pin +['"](?:#{names})['"](?=,|$)/)
81
+ say_status('skipped', "append #{name} to #{IMPORTMAP}", :yellow)
93
82
  else
94
- append_to_file JS_APP do
95
- "import 'active_scaffold'\n"
96
- end
83
+ append_to_file IMPORTMAP, "pin '#{name}'\n"
97
84
  end
98
- manifest = ['active_scaffold/manifest.js']
99
- if Object.const_defined?(:Jquery)
100
- if original_js.match?(/^import +['"]jquery['"]/)
101
- say_status('skipped', "append jquery to #{JS_APP}", :yellow)
102
- else
103
- manifest << 'jquery.js'
104
- insert_into_file JS_APP, before: /import +['"]active_scaffold['"]\n/ do
105
- "import 'jquery'\n"
106
- end
85
+ end
86
+
87
+ def generated_active_scaffold_pin?(pin)
88
+ pin.match?(%r{\Apin +['"]active_scaffold['"](?:, *to: *['"]active_scaffold/load\.js['"])?\z})
89
+ end
90
+
91
+ def add_to_js_app
92
+ original_js = File.binread(JS_APP)
93
+ add_js_import original_js, 'active_scaffold'
94
+ manifest_files = ['active_scaffold/manifest.js']
95
+
96
+ if jquery?
97
+ unless js_import?(original_js, 'jquery')
98
+ add_js_import original_js, 'jquery', before: 'active_scaffold'
99
+ manifest_files << 'jquery.js'
107
100
  end
108
- if original_js.match?(%r{^import +(['"]jquery_ujs['"]|Rails from ['"]@rails/ujs['"])})
109
- say_status('skipped', "append jquery_ujs to #{JS_APP}", :yellow)
110
- else
111
- manifest << 'jquery_ujs.js'
112
- insert_into_file JS_APP, before: /import +['"]active_scaffold['"]\n/ do
113
- "import 'jquery_ujs'\n"
114
- end
101
+ unless original_js.match?(%r{^import +(['"]jquery_ujs['"]|Rails from ['"]@rails/ujs['"])})
102
+ add_js_import original_js, 'jquery_ujs', before: 'active_scaffold'
103
+ manifest_files << 'jquery_ujs.js'
115
104
  end
116
105
  end
117
- add_to_manifest(*manifest)
118
- end
119
-
120
- def create_javascript_manifest(file)
121
- FileUtils.mkdir_p File.dirname(file)
122
- js_content = "// This is a manifest file that'll be compiled into application.js, which will include all the files
123
- // listed below.
124
- //
125
- // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
126
- // vendor/assets/javascripts directory can be referenced here using a relative path.
127
- //
128
- // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
129
- // compiled file. JavaScript code in this file should be added after the last require_* statement.
130
- //
131
- // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
132
- // about supported directives.
133
- //
134
- //= require active_scaffold
135
- "
136
- File.open(file, 'w') { |f| f << js_content } unless options[:pretend]
137
- say_status('create', file)
138
- insert_into_file 'app/views/layouts/application.html.erb', after: /javascript_pack_tag 'application'.*\n/ do
139
- " <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload', 'data-turbo-track': 'reload' %>\n"
140
- end
141
- add_to_manifest 'application.js', 'active_scaffold/manifest.js'
142
- setup_jquery file, js_content, where: 'active_scaffold'
106
+
107
+ add_to_manifest(*manifest_files) if sprockets?
108
+ end
109
+
110
+ def add_js_import(original_js, name, before: nil)
111
+ if js_import?(original_js, name)
112
+ say_status('skipped', "add #{name} to #{JS_APP}", :yellow)
113
+ elsif before
114
+ insert_into_file JS_APP, "import '#{name}'\n", before: /^import +['"]#{Regexp.escape(before)}['"]\n/
115
+ else
116
+ append_to_file JS_APP, "import '#{name}'\n"
117
+ end
118
+ end
119
+
120
+ def js_import?(content, name)
121
+ content.match?(/^import +['"]#{Regexp.escape(name)}['"]/)
122
+ end
123
+
124
+ def add_importmap_layout_tags
125
+ tags = []
126
+ content = layout_content
127
+ tags << '<%= javascript_importmap_tags %>' unless content.include?('javascript_importmap_tags')
128
+ tags << '<%= active_scaffold_javascript_tag %>' unless content.include?('active_scaffold_javascript_tag')
129
+ add_layout_tags tags
130
+ end
131
+
132
+ def add_propshaft_javascript_tags
133
+ tags = []
134
+ content = layout_content
135
+ if jquery? && !content.match?(/javascript_include_tag .*jquery/)
136
+ tags << "<%= javascript_include_tag 'jquery', 'jquery_ujs' %>"
137
+ elsif !jquery?
138
+ say_status('missing', 'no jquery-rails gem; load jquery and jquery_ujs or @rails/ujs in the layout', :red)
139
+ end
140
+ tags << '<%= active_scaffold_javascript_tag %>' unless content.include?('active_scaffold_javascript_tag')
141
+ add_layout_tags tags
142
+ end
143
+
144
+ def layout_content
145
+ unless File.exist?(LAYOUT)
146
+ raise Thor::Error, "Unable to configure ActiveScaffold JavaScript: #{LAYOUT} does not exist"
147
+ end
148
+
149
+ File.binread(LAYOUT)
150
+ end
151
+
152
+ def add_layout_tags(tags)
153
+ return if tags.empty?
154
+
155
+ content = File.binread(LAYOUT)
156
+ unless content.include?('</head>')
157
+ raise Thor::Error, "Unable to configure ActiveScaffold JavaScript: no </head> tag found in #{LAYOUT}"
158
+ end
159
+
160
+ insert_into_file LAYOUT, tags.map { |tag| " #{tag}\n" }.join, before: %r{^[ \t]*</head>}
161
+ end
162
+
163
+ def add_sprockets_javascript
164
+ original_js = File.binread(JS_ASSET)
165
+ unless original_js.match?(%r{//= require +active_scaffold(?:\s|$)})
166
+ append_to_file JS_ASSET, "\n//= require active_scaffold\n"
167
+ end
168
+ setup_sprockets_jquery original_js
169
+ add_to_manifest 'active_scaffold/manifest.js'
170
+ end
171
+
172
+ def use_sprockets_javascript?
173
+ File.exist?(JS_ASSET) && (sprockets? || (!propshaft? && !File.exist?(JS_APP)))
174
+ end
175
+
176
+ def setup_sprockets_jquery(original_js)
177
+ if jquery?
178
+ return if original_js.match?(%r{//= require +jquery(?:\s|$)})
179
+
180
+ insert_into_file JS_ASSET, "//= require jquery\n", before: %r{//= require +active_scaffold(?:\s|$)}
181
+ else
182
+ say_status('missing', 'no jquery-rails gem; load jquery manually before ActiveScaffold', :red)
183
+ end
143
184
  end
144
185
 
145
186
  def add_to_manifest(*files)
146
- append_to_file MANIFEST do
147
- files.map { |file| "//= link #{file}\n" }.join
187
+ original_manifest = File.exist?(MANIFEST) ? File.binread(MANIFEST) : ''
188
+ files.each do |file|
189
+ next if original_manifest.match?(%r{^//= link #{Regexp.escape(file)}$})
190
+
191
+ append_to_file MANIFEST, "//= link #{file}\n"
148
192
  end
149
193
  end
150
194
 
151
- def setup_jquery(file, original_js = nil, where: 'ujs')
152
- original_js ||= File.binread(file)
153
- if Object.const_defined?(:Jquery)
154
- unless original_js.include?('require jquery')
155
- insert_into_file file, before: %r{//= require +.*#{where}['"]?\n} do
156
- "//= require jquery\n"
157
- end
158
- end
195
+ def add_to_scss_asset
196
+ dependency = scss_dependency
197
+ unless dependency
198
+ say_status('missing', "could not detect dartsass-rails or dartsass-sprockets; update #{SCSS_ASSET} manually", :red)
199
+ return
200
+ end
201
+
202
+ original_scss = File.binread(SCSS_ASSET)
203
+ if original_scss.match?(%r{^@use +['"]active_scaffold(?:/core)?['"]})
204
+ say_status('skipped', "add ActiveScaffold to #{SCSS_ASSET}", :yellow)
205
+ else
206
+ prepend_to_file SCSS_ASSET, "@use '#{dependency}';\n"
207
+ end
208
+ end
209
+
210
+ def scss_dependency
211
+ if gem_loaded?('dartsass-sprockets')
212
+ 'active_scaffold'
213
+ elsif gem_loaded?('dartsass-rails')
214
+ 'active_scaffold/core'
215
+ end
216
+ end
217
+
218
+ def add_to_css_asset
219
+ file = CSS_ASSETS.find do |path|
220
+ File.exist?(path) && File.binread(path).match?(/\*= require(?:_tree)?\b/)
221
+ end
222
+ unless file
223
+ say_status('missing', 'no Dart Sass entrypoint or Sprockets stylesheet manifest found', :red)
224
+ return
225
+ end
226
+
227
+ original_css = File.binread(file)
228
+ if original_css.match?(/\*= require +active_scaffold(?:\s|$)/)
229
+ say_status('skipped', "add ActiveScaffold to #{file}", :yellow)
159
230
  else
160
- say_status('missing', 'no jquery-rails gem, load jquery in your layout, or add jquery-rails to Gemfile and add //= require jquery to application.js', :red)
231
+ insert_into_file file, " *= require active_scaffold\n", after: /^[ \t]*\*= require(?:_tree)?\b.*\n/
161
232
  end
162
233
  end
234
+
235
+ def propshaft?
236
+ asset_pipeline == :propshaft
237
+ end
238
+
239
+ def sprockets?
240
+ asset_pipeline == :sprockets
241
+ end
242
+
243
+ def asset_pipeline
244
+ return :propshaft if propshaft_active?
245
+ return :sprockets if sprockets_active?
246
+ return :propshaft if gem_loaded?('propshaft')
247
+
248
+ :sprockets if gem_loaded?('sprockets-rails')
249
+ end
250
+
251
+ def propshaft_active?
252
+ defined?(Propshaft::Assembly) && Rails.application.respond_to?(:assets) &&
253
+ Rails.application.assets.is_a?(Propshaft::Assembly)
254
+ end
255
+
256
+ def sprockets_active?
257
+ defined?(Sprockets::Environment) && Rails.application.respond_to?(:assets) &&
258
+ Rails.application.assets.is_a?(Sprockets::Environment)
259
+ end
260
+
261
+ def jquery?
262
+ Object.const_defined?(:Jquery)
263
+ end
264
+
265
+ def gem_loaded?(name)
266
+ Gem.loaded_specs.key?(name)
267
+ end
163
268
  end
164
269
  end
165
270
  end
@@ -16,7 +16,8 @@ namespace :bundle do
16
16
  gemfiles.each do |gemfile|
17
17
  puts "Locking #{File.basename(gemfile)}..."
18
18
  Bundler.with_unbundled_env do
19
- system("bundle lock --gemfile='#{gemfile}'")
19
+ command = ['bundle', 'lock', "--gemfile=#{gemfile}", '--add-platform', 'ruby']
20
+ system(*command, exception: true)
20
21
  end
21
22
  end
22
23
 
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :mock_app do
4
+ desc 'Build the mock app stylesheets'
5
+ task :assets do
6
+ environment = {
7
+ 'BUNDLE_GEMFILE' => File.expand_path(ENV.fetch('BUNDLE_GEMFILE', 'Gemfile'))
8
+ }
9
+ Dir.chdir('test/mock_app') do
10
+ system(environment, 'rails', 'dartsass:build', exception: true)
11
+ end
12
+ end
13
+
14
+ desc 'Start the mock_app server and Dart Sass watcher (default port 3000)'
15
+ task :server do
16
+ args = ARGV.drop_while { |a| a != '--' }.drop(1)
17
+ Dir.chdir('test/mock_app') do
18
+ environment = {'BUNDLE_GEMFILE' => File.expand_path('../../Gemfile', __dir__)}
19
+ system(environment, 'rails', 'dartsass:build', exception: true)
20
+ watcher = Process.spawn(environment, 'rails', 'dartsass:watch')
21
+ _pid, status = Process.wait2(Process.spawn(environment, 'rails', 'server', *args))
22
+ exit status.exitstatus
23
+ ensure
24
+ begin
25
+ if watcher
26
+ Process.kill('TERM', watcher)
27
+ Process.wait(watcher)
28
+ end
29
+ rescue Errno::ECHILD, Errno::ESRCH
30
+ # The watcher already exited.
31
+ end
32
+ end
33
+ end
34
+
35
+ desc 'Start the mock_app console'
36
+ task :console do
37
+ args = ARGV.drop_while { |a| a != '--' }.drop(1)
38
+ Dir.chdir('test/mock_app') do
39
+ exec({'BUNDLE_GEMFILE' => File.expand_path('../../Gemfile', __dir__)},
40
+ 'rails', 'console', *args)
41
+ end
42
+ end
43
+ end