inline_forms 7.2.11 → 7.9.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +286 -0
  3. data/README.rdoc +14 -2
  4. data/app/assets/javascripts/inline_forms/inline_forms.js +26 -10
  5. data/app/assets/stylesheets/inline_forms/inline_forms.scss +33 -16
  6. data/app/controllers/concerns/versions_concern.rb +2 -3
  7. data/app/controllers/inline_forms_application_controller.rb +5 -1
  8. data/app/controllers/inline_forms_controller.rb +185 -34
  9. data/app/helpers/form_elements/ckeditor.rb +4 -30
  10. data/app/helpers/form_elements/plain_text.rb +23 -0
  11. data/app/helpers/form_elements/plain_text_area.rb +7 -3
  12. data/app/helpers/form_elements/text_area.rb +4 -44
  13. data/app/helpers/form_elements/text_area_without_ckeditor.rb +5 -4
  14. data/app/helpers/form_elements/text_field.rb +2 -2
  15. data/app/helpers/inline_forms_helper.rb +144 -74
  16. data/app/views/devise/sessions/_form.html.erb +4 -1
  17. data/app/views/inline_forms/_close.html.erb +9 -5
  18. data/app/views/inline_forms/_edit.html.erb +8 -41
  19. data/app/views/inline_forms/_list.html.erb +53 -55
  20. data/app/views/inline_forms/_new.html.erb +22 -12
  21. data/app/views/inline_forms/_show.html.erb +13 -37
  22. data/app/views/inline_forms/_versions.html.erb +5 -4
  23. data/app/views/inline_forms/_versions_list.html.erb +8 -12
  24. data/app/views/inline_forms/create_list_frame.html.erb +3 -0
  25. data/app/views/inline_forms/field_edit.html.erb +3 -0
  26. data/app/views/inline_forms/field_show.html.erb +3 -0
  27. data/app/views/inline_forms/new_record.html.erb +3 -0
  28. data/app/views/inline_forms/row_close.html.erb +13 -0
  29. data/app/views/inline_forms/row_destroyed.html.erb +9 -0
  30. data/app/views/inline_forms/row_show.html.erb +3 -0
  31. data/app/views/inline_forms/versions_list_panel.html.erb +3 -0
  32. data/app/views/inline_forms/versions_panel.html.erb +6 -0
  33. data/app/views/layouts/application.html.erb +2 -5
  34. data/app/views/layouts/inline_forms.html.erb +12 -6
  35. data/archived/README.md +47 -0
  36. data/archived/form_elements/README.md +27 -0
  37. data/archived/form_elements/chicas/README.md +31 -0
  38. data/archived/form_elements/geo_code_curacao/README.md +62 -0
  39. data/{app → archived/form_elements/geo_code_curacao/app}/helpers/form_elements/geo_code_curacao.rb +0 -1
  40. data/archived/form_elements/geo_code_curacao/app/views/geo_code_curacao/list_streets.html.erb +1 -0
  41. data/archived/form_elements/geo_code_curacao/app/views/geo_code_curacao/list_streets.js.erb +1 -0
  42. data/archived/form_elements/kansen_slider/README.md +31 -0
  43. data/archived/form_elements/tree/README.md +47 -0
  44. data/archived/form_elements/tree/app/views/inline_forms/_show_tree.html.erb +30 -0
  45. data/{app → archived/form_elements/tree/app}/views/inline_forms/_tree.html.erb +18 -5
  46. data/bin/inline_forms +22 -1
  47. data/bin/inline_forms_installer_core.rb +108 -8
  48. data/docs/ujs-to-turbo.md +192 -0
  49. data/lib/generators/USAGE +2 -2
  50. data/lib/generators/assets/stylesheets/inline_forms.scss +33 -16
  51. data/lib/inline_forms/archived_form_elements.rb +70 -0
  52. data/lib/inline_forms/version.rb +1 -1
  53. data/lib/inline_forms.rb +60 -2
  54. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_field_turbo_test.rb +73 -0
  55. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_list_test.rb +73 -0
  56. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_required_test.rb +21 -0
  57. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_photos_pagination_test.rb +227 -15
  58. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_row_turbo_test.rb +103 -0
  59. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_top_level_new_test.rb +70 -0
  60. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_top_level_pagination_test.rb +40 -0
  61. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_versions_turbo_test.rb +120 -0
  62. data/lib/installer_templates/example_app_tests/test/integration/example_app_photo_revert_test.rb +94 -0
  63. data/lib/installer_templates/example_app_tests/test/integration/example_app_turbo_layout_test.rb +6 -9
  64. data/lib/installer_templates/example_app_tests/test/models/example_app_apartment_name_validation_test.rb +16 -0
  65. data/lib/installer_templates/example_app_tests/test/models/example_app_plain_text_rich_text_edge_cases_test.rb +46 -0
  66. data/lib/installer_templates/example_app_views/apartments/name_list.html.erb +26 -0
  67. data/lib/installer_templates/example_app_views/inline_forms/_header.html.erb +45 -0
  68. data/test/archived_form_elements_test.rb +41 -0
  69. data/test/form_element_from_callee_test.rb +2 -2
  70. data/test/inline_forms_generator_test.rb +10 -0
  71. data/test/plain_text_configuration_test.rb +90 -0
  72. metadata +45 -24
  73. data/app/views/geo_code_curacao/list_streets.html.erb +0 -1
  74. data/app/views/geo_code_curacao/list_streets.js.erb +0 -1
  75. data/app/views/inline_forms/close.js.erb +0 -4
  76. data/app/views/inline_forms/edit.js.erb +0 -1
  77. data/app/views/inline_forms/list.js.erb +0 -1
  78. data/app/views/inline_forms/new.js.erb +0 -1
  79. data/app/views/inline_forms/record_destroyed.js.erb +0 -1
  80. data/app/views/inline_forms/show.js.erb +0 -1
  81. data/app/views/inline_forms/show_element.js.erb +0 -1
  82. data/app/views/inline_forms/show_undo.js.erb +0 -1
  83. data/app/views/inline_forms/update.js.erb +0 -1
  84. data/app/views/inline_forms/versions.js.erb +0 -4
  85. data/app/views/inline_forms/versions_list.js.erb +0 -1
  86. data/lib/generators/assets/javascripts/ckeditor/config.js +0 -72
  87. /data/{app → archived/form_elements/chicas/app}/helpers/form_elements/chicas_dropdown_with_family_members.rb +0 -0
  88. /data/{app → archived/form_elements/chicas/app}/helpers/form_elements/chicas_family_photo_list.rb +0 -0
  89. /data/{app → archived/form_elements/chicas/app}/helpers/form_elements/chicas_photo_list.rb +0 -0
  90. /data/{app → archived/form_elements/geo_code_curacao/app}/controllers/geo_code_curacao_controller.rb +0 -0
  91. /data/{app → archived/form_elements/geo_code_curacao/app}/models/geo_code_curacao.rb +0 -0
  92. /data/{app → archived/form_elements/kansen_slider/app}/helpers/form_elements/kansen_slider.rb +0 -0
  93. /data/{app → archived/form_elements/tree/app}/helpers/form_elements/move.rb +0 -0
data/bin/inline_forms CHANGED
@@ -110,7 +110,28 @@ module InlineForms
110
110
 
111
111
  app_template_file = File.join(File.dirname(__FILE__), 'inline_forms_app_template.rb')
112
112
 
113
- if ! run("rails new #{app_name} -m #{app_template_file} --skip-bundle --skip-bootsnap --javascript=importmap")
113
+ # Prefer a Rails 7.0.x generator when one is installed locally: the
114
+ # generated `Gemfile`/`config/application.rb` pin to `rails ~> 7.0.0`,
115
+ # so running `rails new` from a newer system Rails (e.g. 8.0.x) emits
116
+ # 7.1+/8.0-only configuration (`load_defaults 8.0`,
117
+ # `config.autoload_lib(...)`) that aborts on the next `bundle exec`.
118
+ # The installer_core also patches `config/application.rb` defensively;
119
+ # the explicit version selector is the cleaner first line of defense.
120
+ require 'rubygems'
121
+ compatible_rails =
122
+ begin
123
+ Gem::Specification
124
+ .find_all_by_name("rails")
125
+ .map(&:version)
126
+ .select { |v| v >= Gem::Version.new("7.0") && v < Gem::Version.new("7.1") }
127
+ .max
128
+ rescue StandardError
129
+ nil
130
+ end
131
+ rails_invocation = compatible_rails ? "rails _#{compatible_rails}_" : "rails"
132
+ say "Generating app with: #{rails_invocation} new ...", :green
133
+
134
+ if ! run("#{rails_invocation} new #{app_name} -m #{app_template_file} --skip-bundle --skip-bootsnap --javascript=importmap")
114
135
  say "Rails could not create the app '#{app_name}', maybe because it is a reserved word...", :red # TODO ROYTJE MAKE ERROR MESSAGE MORE RELEVANT # Rails could not create the app 'MyApp', maybe because it is a reserved word..
115
136
  exit 1
116
137
  end
@@ -5,12 +5,33 @@ GENERATOR_PATH = File.dirname(File.expand_path(__FILE__)) + '/../'
5
5
  remove_file 'Gemfile' if File.exist?('Gemfile')
6
6
  create_file 'Gemfile', "# created by inline_forms #{ENV['inline_forms_version']} on #{Date.today}\n"
7
7
 
8
+ # `rails new` is invoked with whatever the system `rails` binary points at
9
+ # (often Rails 8.x once it lands in the global gemset), so the generated
10
+ # `config/application.rb` may carry Rails 7.1+/8.0 idioms (`load_defaults
11
+ # 8.0`, `config.autoload_lib(...)`). The Gemfile we write below pins
12
+ # `rails ~> 7.0.0`, so Rails 7.0 must be able to interpret application.rb;
13
+ # otherwise the first `bundle exec rails …` aborts with `Unknown version
14
+ # "8.0"` or `NoMethodError: undefined method 'autoload_lib'`.
15
+ if File.exist?('config/application.rb')
16
+ gsub_file 'config/application.rb',
17
+ /config\.load_defaults\s+\d+\.\d+/,
18
+ 'config.load_defaults 7.0'
19
+ # Strip Rails 7.1+ `config.autoload_lib(ignore: ...)` (and any surrounding
20
+ # explanatory comment block). Not supported on Rails 7.0.
21
+ gsub_file 'config/application.rb',
22
+ /^\s*#[^\n]*\n(\s*#[^\n]*\n)*\s*config\.autoload_lib\([^)]*\)\s*\n/,
23
+ ""
24
+ gsub_file 'config/application.rb',
25
+ /^\s*config\.autoload_lib\([^)]*\)\s*\n/,
26
+ ""
27
+ end
28
+
8
29
  add_source 'https://rubygems.org'
9
30
 
10
31
  gem 'cancancan'
11
32
  gem 'carrierwave', '~> 3.1'
12
- gem 'devise-i18n', :git => 'https://github.com/acesuares/devise-i18n.git'
13
- gem 'devise'
33
+ gem 'devise', '~> 5.0'
34
+ gem 'devise-i18n', '~> 1.16'
14
35
  gem 'autoprefixer-rails'
15
36
  # foundation-rails 6.7+ uses Dart Sass (`sass:math`); sass-rails/sassc removed.
16
37
  # Visually tuned against foundation-rails ~> 6.6.2; current pin ~> 6.9 (6.9.0.x).
@@ -36,7 +57,6 @@ gem 'rails-i18n', '~> 7.0'
36
57
  gem 'rails-jquery-autocomplete'
37
58
  gem 'rails', '~> 7.0.0'
38
59
  gem 'rake'
39
- gem 'remotipart', '~> 1.0'
40
60
  gem 'rvm'
41
61
  gem 'dartsass-rails'
42
62
  # Rails 7 no longer adds sprockets-rails to the default Gemfile; declare it
@@ -45,10 +65,9 @@ gem 'dartsass-rails'
45
65
  gem 'sprockets-rails'
46
66
  # Rails 7 default JavaScript tooling: importmap-rails replaces Webpacker.
47
67
  gem 'importmap-rails'
48
- # Hotwire/Turbo. Loaded into the Sprockets bundle by inline_forms.js with
49
- # Turbo Drive disabled by default so existing UJS-driven links/forms keep
50
- # working; turbo-rails also registers the `turbo_stream` Mime type and view
51
- # format so future controllers can opt in to Turbo Stream responses.
68
+ # Hotwire/Turbo. Loaded from layouts as `<script type="module">`; inline flows
69
+ # use `<turbo-frame>` + HTML responses (see docs/ujs-to-turbo.md). Registers the
70
+ # `turbo_stream` MIME type for optional stream responses.
52
71
  gem 'turbo-rails'
53
72
  gem 'tabs_on_rails', :git => 'https://github.com/acesuares/tabs_on_rails.git', :branch => 'update_remote_before_action'
54
73
  gem 'unicorn'
@@ -443,7 +462,7 @@ sleep 1 # unique migration timestamps per generator
443
462
  generate "inline_forms", "InlineFormsKey name:string inline_forms_translations:has_many inline_forms_translations:associated _enabled:yes _presentation:\#{name}"
444
463
  sleep 1
445
464
  generate "inline_forms", "InlineFormsTranslation inline_forms_key:belongs_to inline_forms_locale:dropdown value:text interpolations:text is_proc:boolean _presentation:\#{value}"
446
- # TODO: fix text_area into text_area_without_ckeditor
465
+ # Plain long text uses :plain_text; ActionText-backed fields use :rich_text.
447
466
  sleep 1 # to get unique migration number
448
467
  create_file "db/migrate/" +
449
468
  Time.now.utc.strftime("%Y%m%d%H%M%S") +
@@ -661,6 +680,73 @@ if ENV['install_example'] == 'true'
661
680
  run 'bundle exec rails generate uploader Image'
662
681
  run 'bundle exec rails g inline_forms Apartment name:string title:string description:rich_text photos:has_many photos:associated _enabled:yes _presentation:\'#{name}\''
663
682
 
683
+ say "- Apartment name is required..."
684
+ inject_into_file "app/models/apartment.rb",
685
+ "\n validates :name, presence: true\n",
686
+ after: " has_paper_trail\n"
687
+
688
+ # CarrierWave + PaperTrail history.
689
+ # PaperTrail snapshots the column scalar (the stored filename) on update,
690
+ # but CarrierWave's default `remove_previously_stored_files_after_update`
691
+ # deletes the old file on disk and re-uses the same filename, so a
692
+ # PaperTrail revert restores a filename whose bytes are gone.
693
+ # We keep every uploaded file on disk and namespace filenames with a
694
+ # per-upload token so successive uploads do not collide. See
695
+ # https://stackoverflow.com/questions/9423279/papertrail-and-carrierwave
696
+ # (Answers 2, 4 and 5).
697
+ say "- Configuring CarrierWave to keep previously stored files (PaperTrail history)..."
698
+ create_file "config/initializers/carrierwave.rb", <<-CWINIT.strip_heredoc
699
+ # Keep previously stored files on disk so PaperTrail-driven restore
700
+ # actually returns the previous image bytes. See
701
+ # https://stackoverflow.com/questions/9423279/papertrail-and-carrierwave
702
+ # The per-uploader overrides in app/uploaders/image_uploader.rb
703
+ # complement this by giving every upload a unique on-disk filename
704
+ # and by no-op'ing `remove!` so destroyed records keep their files.
705
+ CarrierWave.configure do |config|
706
+ config.remove_previously_stored_files_after_update = false
707
+ end
708
+ CWINIT
709
+
710
+ inject_into_file "app/uploaders/image_uploader.rb",
711
+ after: "class ImageUploader < CarrierWave::Uploader::Base\n" do
712
+ <<-RUBY.strip_heredoc.gsub(/^/, " ")
713
+ # PaperTrail history support. CarrierWave's default behaviour wipes the
714
+ # previous file on update and reuses the same filename; PaperTrail only
715
+ # stores the column scalar, so a plain `version.reify; save!` restores a
716
+ # filename whose bytes are gone. The knobs below preserve every byte:
717
+ #
718
+ # * `remove_previously_stored_files_after_update = false` is set
719
+ # globally in config/initializers/carrierwave.rb (covers
720
+ # `multi_image_field` uploaders too).
721
+ # * `remove!` is a no-op so hard-destroyed records keep their files
722
+ # and revert-after-destroy still finds the bytes on disk.
723
+ # * `filename` is prefixed with a per-upload UUID so successive
724
+ # uploads never collide on disk.
725
+ #
726
+ # Trade-off: files accumulate on disk; sweeping is out of scope.
727
+ # Source: https://stackoverflow.com/questions/9423279/papertrail-and-carrierwave
728
+ def remove!
729
+ # no-op: keep the file so PaperTrail revert can restore it.
730
+ end
731
+
732
+ def filename
733
+ # CarrierWave 3.x calls `filename` again after storing to record the
734
+ # persisted name; at that point `original_filename` may be nil and we
735
+ # must still return the memoized name (see
736
+ # https://github.com/carrierwaveuploader/carrierwave/issues/2708).
737
+ @name ||= "\#{secure_token}-\#{original_filename}" if original_filename
738
+ @name
739
+ end
740
+
741
+ private
742
+
743
+ def secure_token
744
+ var = :"@\#{mounted_as}_secure_token"
745
+ model.instance_variable_get(var) || model.instance_variable_set(var, SecureRandom.uuid)
746
+ end
747
+ RUBY
748
+ end
749
+
664
750
  say "- Lower Photo.per_page so the seeded gallery paginates..."
665
751
  # The model template (lib/generators/templates/model.erb) emits
666
752
  # attr_reader :per_page
@@ -756,6 +842,19 @@ if ENV['install_example'] == 'true'
756
842
 
757
843
  remove_file 'public/index.html'
758
844
 
845
+ say "- Apartment name list demo (field-level inline edit without _show)..."
846
+ inject_into_file "app/controllers/apartments_controller.rb",
847
+ "\n skip_load_and_authorize_resource only: :name_list\n\n def name_list\n authorize! :read, Apartment\n @apartments = Apartment.accessible_by(current_ability).order(:id).limit(10)\n end\n",
848
+ after: "set_tab :apartment\n"
849
+
850
+ example_views_root = File.join(GENERATOR_PATH, "lib/installer_templates/example_app_views")
851
+ Dir.glob(File.join(example_views_root, "**", "*")).sort.each do |abs|
852
+ next unless File.file?(abs)
853
+ rel = abs.delete_prefix(example_views_root + File::SEPARATOR).tr("\\", "/")
854
+ create_file File.join("app/views", rel), File.read(abs)
855
+ end
856
+
857
+ route 'get "apartments/name_list", to: "apartments#name_list", as: :apartment_name_list'
759
858
  route "root :to => 'apartments#index'"
760
859
 
761
860
  say "- Adding example app regression tests (bundle exec rails test)..."
@@ -768,6 +867,7 @@ if ENV['install_example'] == 'true'
768
867
  say "\nDone! Example app (Photo + Apartment) is ready.", :yellow
769
868
  say " bundle exec rails test # example regression tests", :yellow
770
869
  say " bundle exec rails s # then http://localhost:3000/apartments", :yellow
870
+ say " More menu → Apartment names (first 10) # /apartments/name_list", :yellow
771
871
  say " Log in: #{ENV["email"]} / #{ENV["password"]}", :yellow
772
872
  end
773
873
  # done!
@@ -0,0 +1,192 @@
1
+ # UJS → Turbo migration checklist
2
+
3
+ Track progress toward full Turbo integration and removal of jQuery UJS from inline_forms generated apps.
4
+
5
+ **Current gem version:** see `lib/inline_forms/version.rb` (**Step 5** — Turbo Drive on, jquery-ujs / remotipart removed in **7.8.0**)
6
+
7
+ **Architecture today:** inline interactions use **`<turbo-frame>`** + **`format.html`** (and optional **`format.turbo_stream`**). Turbo loads as an ES module from the layouts; **Turbo Drive** uses the library default (**enabled**). No `*.js.erb` in active engine views; no `jquery_ujs` or `jquery.remotipart` in the Sprockets bundle.
8
+
9
+ **Target end state:** Stock flows use Turbo Frames + HTML; optional `turbo_stream` where it simplifies a response. jQuery UJS and `*.js.erb` are gone from the engine.
10
+
11
+ ---
12
+
13
+ ## Step 1 — Turbo wired (DONE)
14
+
15
+ - [x] `gem 'turbo-rails'` in installer Gemfile (`bin/inline_forms_installer_core.rb`)
16
+ - [x] Turbo loaded as `<script type="module">` in `layouts/inline_forms.html.erb` and `layouts/application.html.erb`
17
+ - [x] `Turbo.session.drive` uses the **default (enabled)** — **7.8.0** (was disabled while UJS coexisted)
18
+ - [x] Turbo **not** in Sprockets bundle (`app/assets/javascripts/inline_forms/inline_forms.js`) — ESM parse-error lesson from 7.1.1
19
+ - [x] Smoke test: `lib/installer_templates/example_app_tests/test/integration/example_app_turbo_layout_test.rb`
20
+
21
+ ---
22
+
23
+ ## Step 2 — Nested list + pagination as Turbo Frame (DONE)
24
+
25
+ - [x] `_list.html.erb`: nested has_many container is `<turbo-frame id="…_list">` when `parent_class` present
26
+ - [x] Nested pagination: no `:remote => true`; `update=` param matches frame id (`…_list` suffix)
27
+ - [x] `InlineFormsController#index`: HTML for nested frame requests; `turbo_rails/frame` vs `inline_forms` layout negotiation
28
+ - [x] **Nested rows (7.4.2):** per-row `<turbo-frame>` + Turbo presentation links; **`row_html_turbo_allowed?`** enables **`format.html`** row open/close for **`not_accessible_through_html?`** models when **`params[:update]`** is a nested associated row id (`apartment_<aid>_photo_<pid>`). Removed row-level **`data-turbo="false"`** (field cancel + pagination use Turbo inside nested frames).
29
+ - [x] Top-level rows must **not** carry `data-turbo="false"` (would poison inner frames — 7.2.3)
30
+ - [x] Smoke test: `example_app_apartment_photos_pagination_test.rb`
31
+ - [x] Example seed data (Konferensha + photos) for pagination assertions
32
+
33
+ **Explicitly deferred in this step:** top-level index lists (`/apartments`) stay full-page + UJS row open.
34
+
35
+ ---
36
+
37
+ ## Step 3 — Per-row / per-field inline-edit lifecycle
38
+
39
+ Convert the **`show → edit → update → show_element → close`** cycle without UJS.
40
+
41
+ ### DOM contract (unchanged)
42
+
43
+ - Every editable region needs a stable id: `{model}_{id}_{attribute}` (e.g. `apartment_5_name`)
44
+ - `params[:update]` must match that id (set by `link_to_inline_edit`, `_edit.html.erb`, etc.)
45
+
46
+ ### Row-level (stock list → full `_show` panel)
47
+
48
+ - [x] Wrap each top-level list row in `<turbo-frame id="apartment_<id>">` (`_list.html.erb` when `parent_class` is nil)
49
+ - [x] Row title link: GET `show` → HTML `row_show` / `_show` inside frame (no `:remote`; `data-turbo` + `data-turbo-frame`)
50
+ - [x] `InlineFormsController#show` (full record, no `params[:attribute]`): `format.html` → `row_show` / `row_close` + `turbo_rails/frame` when `turbo_frame_request?`, else full `inline_forms` layout
51
+ - [x] `close_link` and `_close` presentation link: Turbo when `@inline_forms_turbo_row` (row HTML path)
52
+ - [x] `soft_delete`, `soft_restore`, `destroy`, `revert`: `format.html` → `row_close` / `row_destroyed` (+ Turbo toolbar links)
53
+ - [x] Remove `edit.js.erb`, `update.js.erb`, `show_element.js.erb` (field lifecycle is Turbo HTML only)
54
+ - [x] Remove `show.js.erb`, `close.js.erb`, `record_destroyed.js.erb`, `show_undo.js.erb` (7.7.0; tree migrated)
55
+ - [x] Remove `:remote => true` from nested `_list` / `_close` / toolbar where migrated (row toolbar, versions, nested `+` use Turbo; top-level `new` was Step 4)
56
+ - [x] Remove `data-turbo="false"` from nested rows once nested inline-edit is Turbo-native (7.4.2)
57
+
58
+ ### Nested associated lists (e.g. Apartment → Photo)
59
+
60
+ - [x] Each nested row is `<turbo-frame id="{parent}_{id}_{assoc}_{child_id}">` with Turbo presentation links (same contract as top-level).
61
+ - [x] **`row_html_turbo_allowed?`:** `format.html` row open/close for **`not_accessible_through_html?`** models when **`params[:update]`** matches a nested associated row id (≥4 underscore segments, trailing numeric id).
62
+ - [x] Scalar field edit/cancel inside nested **`_show`** uses existing **`format.html`** field templates (no **`UnknownFormat`** for Photo).
63
+
64
+ ### Field-level (`link_to_inline_edit` / `*_show` helpers)
65
+
66
+ - [x] **Stock `_show` scalar fields**: wrapped in `<turbo-frame id="{model}_{id}_{attribute}">`; `@inline_forms_turbo_field = true` so `link_to_inline_edit` omits `:remote => true`
67
+ - [x] **`link_to_inline_edit`**: uses Turbo when `@inline_forms_turbo_field` or explicit `turbo_frame: true`
68
+ - [x] **`InlineFormsController#edit`, `#update`, `#show`** (single attribute): `format.html` + `field_edit` / `field_show` templates when `turbo_frame_request?`
69
+ - [x] **`_edit.html.erb`**: omits `:remote => true` when `@turbo_frame` (Turbo form submit inside frame)
70
+ - [x] Remove `edit.js.erb`, `update.js.erb`, `show_element.js.erb`
71
+ - [x] **`not_accessible_through_html?` models** (e.g. Photo): nested list row **`show` / `close`** use **`format.html`** when **`params[:update]`** is a nested row id (`row_html_turbo_allowed?`); field **`edit` / `update` / `show`** already always register **`format.html`**.
72
+
73
+ ### Widget re-init after swap
74
+
75
+ - [x] ActionText/Trix: `turbo:frame-load` in `inline_forms.js` attaches Trix editors after frame replace
76
+ - [x] jQuery UI datepicker / timepicker: re-bound on `turbo:frame-load` (autocomplete widgets in field partials still use inline scripts until Step 5)
77
+
78
+ ### Helpers (`app/helpers/inline_forms_helper.rb`)
79
+
80
+ - [x] `close_link`, `link_to_soft_delete`, `link_to_destroy`, `link_to_new_record`, `link_to_versions_list`, `close_versions_list_link`: Turbo frame attrs only (**7.8.0**; `turbo_row:` retained for API compat)
81
+
82
+ ### Tests
83
+
84
+ - [x] Integration: stock row open/close on `/apartments` (Turbo frame + HTML `show` / `close`): `example_app_apartment_row_turbo_test.rb`
85
+ - [x] Integration: open apartment row → edit text field → save → cancel (field flow: `example_app_apartment_field_turbo_test.rb`)
86
+ - [x] Integration: nested Photo row open/close + name field cancel (`example_app_apartment_photos_pagination_test.rb`)
87
+ - [x] Integration: replace photo image (multipart) inside nested frame (`example_app_apartment_photos_pagination_test.rb`)
88
+ - [x] Integration: custom field-only page (`ApartmentsController#name_list`) — Turbo edit/update/cancel without full `_show`
89
+ - [x] Assert no `406 UnknownFormat` on Turbo field update (name list test)
90
+ - [x] Integration: row destroy + PaperTrail revert via Turbo (`example_app_apartment_row_turbo_test.rb`)
91
+ - [x] Integration: versions panel open/close via Turbo (`example_app_apartment_versions_turbo_test.rb`)
92
+
93
+ ---
94
+
95
+ ## Step 4 — Remaining UJS surfaces
96
+
97
+ ### Lists and create flow
98
+
99
+ - [x] Top-level index: wrap in `<turbo-frame id="apartments_list">`; in-frame pagination (7.7.0)
100
+ - [x] `new` / `create`: frame refresh replacing list container (7.7.0)
101
+ - [x] Remove `new.js.erb`, `list.js.erb` (7.7.0)
102
+ - [x] `_new.html.erb`: Turbo path when `@turbo_frame` (no `:remote` on cancel)
103
+
104
+ ### Tree
105
+
106
+ - [x] ~~`:tree` / `_tree.html.erb`~~ — **archived in 7.7.0** (`archived/form_elements/tree/`); needs host tree gem/APIs. Turbo-ready partial kept in archive.
107
+
108
+ ### Versions panel
109
+
110
+ - [x] `VersionsConcern#list_versions`: HTML frame (`versions_panel` / `versions_list_panel`, `turbo_rails/frame` layout) — **7.7.3** drops `format.js`
111
+ - [x] Remove `versions.js.erb`, `versions_list.js.erb` (**7.7.3**)
112
+ - [x] `_versions_list.html.erb`: revert uses `inline_forms_turbo_link_data` (row frame), not `:remote => true`
113
+
114
+ ### Geo / misc
115
+
116
+ - [x] ~~`geo_code_curacao`~~ — **archived in 7.6.0** (`archived/form_elements/geo_code_curacao/`). If restored, migrate `list_streets.js.erb` to frame or stream.
117
+
118
+ ### Controller cleanup
119
+
120
+ - [x] Every action in `InlineFormsController` + `VersionsConcern` has a non-JS response path (**7.8.0** audit)
121
+ - [ ] Audit `respond_to` blocks: parallel `format.turbo_stream` where stream is cleaner than full frame (optional)
122
+
123
+ ### Tests
124
+
125
+ - [x] Top-level list pagination in frame (`example_app_apartment_top_level_pagination_test.rb`)
126
+ - [x] Create apartment → list frame updates (`example_app_apartment_top_level_new_test.rb`)
127
+ - [x] Versions panel open/close + revert from list (`example_app_apartment_versions_turbo_test.rb`, **7.7.3**)
128
+ - [x] Assert zero `data-remote="true"` in rendered HTML for inline_forms flows (helpers + `_new` / `_close` no longer fall back to UJS — **7.8.0**)
129
+
130
+ ---
131
+
132
+ ## Step 5 — Enable Drive, remove UJS (DONE in **7.8.0**)
133
+
134
+ - [x] Remove `Turbo.session.drive = false` from both layouts (Drive default **on**)
135
+ - [x] Delete `//= require jquery_ujs` from `inline_forms.js`
136
+ - [x] Delete `//= require jquery.remotipart` from `inline_forms.js`
137
+ - [x] Delete all `app/views/inline_forms/*.js.erb` (completed by **7.7.3**)
138
+ - [x] Remove `format.js` branches from controllers (none remain on stock actions)
139
+ - [x] Update `example_app_turbo_layout_test.rb`: refute `Turbo.session.drive = false`
140
+ - [x] Full `bundle exec rails test` in `--example` app (before release)
141
+
142
+ ### jQuery (optional follow-up — not required to drop UJS)
143
+
144
+ These can remain while UJS is gone; separate migration if desired:
145
+
146
+ - [ ] `jquery.ui.all` (datepicker)
147
+ - [ ] `jquery.timepicker.js`
148
+ - [ ] `autocomplete-rails`
149
+ - [ ] `foundation` jQuery plugin → consider Foundation JS without jQuery or Stimulus
150
+
151
+ ---
152
+
153
+ ## Reference — UJS inventory
154
+
155
+ ### `*.js.erb` (all to delete by Step 5)
156
+
157
+ | File | Effect |
158
+ |------|--------|
159
+ | `show.js.erb` | Replace row with `_show` |
160
+ | `edit.js.erb` | Replace `#update_span` with `_edit` form |
161
+ | `update.js.erb` | Replace with `{form_element}_show` |
162
+ | `show_element.js.erb` | Single-field show after update |
163
+ | `new.js.erb` | Replace with `_new` form |
164
+ | `list.js.erb` | Replace list container |
165
+ | `close.js.erb` | Fade + `_close` partial |
166
+ | `record_destroyed.js.erb` | Fade out row |
167
+ | `show_undo.js.erb` | Undo link after destroy |
168
+ | ~~`versions.js.erb`~~ | Removed **7.7.3** — versions panel HTML frame |
169
+ | ~~`versions_list.js.erb`~~ | Removed **7.7.3** — versions list HTML frame |
170
+ | ~~`geo_code_curacao/list_streets.js.erb`~~ | Archived 7.6.0 — street autocomplete JSON |
171
+
172
+ ### Controller actions still on `format.js`
173
+
174
+ None on `InlineFormsController` / `VersionsConcern` stock actions (**7.8.0**). Host app controllers may still use `format.js`.
175
+
176
+ ### Key files
177
+
178
+ - `app/controllers/inline_forms_controller.rb`
179
+ - `app/controllers/concerns/versions_concern.rb`
180
+ - `app/views/inline_forms/_list.html.erb`, `_show.html.erb`, `_edit.html.erb`, `_new.html.erb`
181
+ - `app/helpers/inline_forms_helper.rb`
182
+ - `app/helpers/form_elements/*.rb` (`*_show` → `link_to_inline_edit`)
183
+ - `app/assets/javascripts/inline_forms/inline_forms.js`
184
+ - `app/views/layouts/inline_forms.html.erb`
185
+
186
+ ---
187
+
188
+ ## Custom field-only pages (helper bypass)
189
+
190
+ Stock `_show` / `_list` are not required for inline edit. Any page can call form-element helpers (e.g. `text_field_show(apartment, :name)`) inside a container with id `apartment_<id>_name`. Edit/update still hit `ApartmentsController#edit` / `#update` via polymorphic paths.
191
+
192
+ Example app **`--example` name list** (`GET /apartments/name_list`): custom page using the **same** turbo-field contract as stock `_show` (not a separate code path). Linked from the **More** menu; regression-tested after stock field Turbo lands.
data/lib/generators/USAGE CHANGED
@@ -3,7 +3,7 @@ Description:
3
3
  We will generate a model, a migration, a controller and a route. Please do check the migration and the model because they will likely need tweaking.
4
4
 
5
5
  Example:
6
- rails generate inline_forms Thing name:text_field description:text_area yesno:check_box gender:boolean_with_values
6
+ rails generate inline_forms Thing name:text_field description:plain_text yesno:check_box gender:boolean_with_values
7
7
 
8
8
  This will create:
9
9
  create app/models/thing.rb
@@ -19,7 +19,7 @@ This will create:
19
19
  def inline_forms_field_list
20
20
  [
21
21
  [ :name, 'name', :text_field ],
22
- [ :description, 'description', :text_area ],
22
+ [ :description, 'description', :plain_text ],
23
23
  [ :yesno, 'yesno', :check_box ],
24
24
  [ :gender, 'gender', :boolean_with_values ],
25
25
  ]
@@ -223,6 +223,15 @@ select:hover, select:focus {
223
223
  font-weight: bold;
224
224
  font-size: 110%;
225
225
  }
226
+ // Custom elements default to `display: inline`, which collapses the row layout
227
+ // of a top-level `<turbo-frame id="apartments_list">` inside `#outer_container`.
228
+ turbo-frame {
229
+ display: block;
230
+ }
231
+ #outer_container > turbo-frame.list_container {
232
+ display: block;
233
+ width: 100%;
234
+ }
226
235
  .list_container {
227
236
  .row {
228
237
  font-size: 1.2rem;
@@ -258,6 +267,30 @@ select:hover, select:focus {
258
267
  }
259
268
  }
260
269
 
270
+ // Field edit cancel: outer <a> carries Turbo/UJS; inner input[type=button] matches ok height.
271
+ .edit_form .row.collapse {
272
+ input[type="submit"].postfix.button,
273
+ a.inline_forms-field-cancel input[type="button"].postfix.button {
274
+ margin: 2px 0 !important;
275
+ }
276
+ }
277
+
278
+ .edit_form a.inline_forms-field-cancel {
279
+ display: inline-block;
280
+ padding: 0;
281
+ border: 0;
282
+ background: transparent;
283
+ line-height: 0;
284
+ vertical-align: top;
285
+ text-decoration: none;
286
+
287
+ input[type="button"] {
288
+ pointer-events: none;
289
+ cursor: pointer;
290
+ width: auto;
291
+ }
292
+ }
293
+
261
294
  .object_presentation {
262
295
  background-color: #B94C32;
263
296
  color: white;
@@ -434,22 +467,6 @@ select:hover, select:focus {
434
467
  margin-bottom: 0.5em;
435
468
  }
436
469
 
437
- .ckeditor_area {
438
- position: relative;
439
- }
440
-
441
- .ckeditor_area .glass_plate {
442
- position: absolute;
443
- top: -1px;
444
- width: 98%;
445
- height: 232px;
446
- border: 0;
447
- }
448
-
449
- .ckeditor_area .cke_top, .ckeditor_area .cke_bottom, .ckeditor_area .cke_border {
450
- display: none;
451
- }
452
-
453
470
  /* jQuery ui Slider 8 */
454
471
  .slider {
455
472
  width: 300px;
@@ -0,0 +1,70 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module InlineForms
3
+ class ArchivedFormElementError < StandardError; end
4
+
5
+ # Retired form-element symbols. Full source for entries with +archive_path+ lives
6
+ # under archived/form_elements/<name>/ — see archived/README.md.
7
+ ARCHIVED_FORM_ELEMENTS = {
8
+ geo_code_curacao: {
9
+ archived_in_version: "7.6.0",
10
+ archive_path: "archived/form_elements/geo_code_curacao",
11
+ summary: "Curaçao street geocode (MySQL Zones/Buurten/Straatcode, jQuery autocomplete, UJS list_streets).",
12
+ },
13
+ chicas_photo_list: {
14
+ archived_in_version: "7.6.0",
15
+ archive_path: "archived/form_elements/chicas",
16
+ summary: "Chicas app read-only member photo gallery (show-only).",
17
+ },
18
+ chicas_family_photo_list: {
19
+ archived_in_version: "7.6.0",
20
+ archive_path: "archived/form_elements/chicas",
21
+ summary: "Chicas app read-only family member photo gallery (show-only).",
22
+ },
23
+ chicas_dropdown_with_family_members: {
24
+ archived_in_version: "7.6.0",
25
+ archive_path: "archived/form_elements/chicas",
26
+ summary: "Chicas client picker via family.clients; moves CarrierWave upload dir on update.",
27
+ },
28
+ kansen_slider: {
29
+ archived_in_version: "7.6.0",
30
+ archive_path: "archived/form_elements/kansen_slider",
31
+ summary: "jQuery UI slider for integer-coded chance scale; uses model attribute_values.",
32
+ },
33
+ tree: {
34
+ archived_in_version: "7.7.0",
35
+ archive_path: "archived/form_elements/tree",
36
+ summary: "Self-referential children list via parent.children; requires host tree APIs (see README).",
37
+ },
38
+ move: {
39
+ archived_in_version: "7.7.0",
40
+ archive_path: "archived/form_elements/tree",
41
+ summary: "Reparent via hash_tree_to_collection + add_child (host must implement; pairs with :tree).",
42
+ },
43
+ absence_list: {
44
+ removed_in_version: "6.3.0",
45
+ archive_path: nil,
46
+ summary: "Project-specific absence list UI; removed without a copy in this repo (see CHANGELOG 6.3.0).",
47
+ },
48
+ }.freeze
49
+
50
+ def self.validate_no_archived_form_elements_for!(klass)
51
+ return unless klass.instance_methods.include?(:inline_forms_attribute_list)
52
+
53
+ klass.new.inline_forms_attribute_list.each do |attribute, _label, form_element|
54
+ key = form_element.to_sym
55
+ next unless ARCHIVED_FORM_ELEMENTS.key?(key)
56
+
57
+ meta = ARCHIVED_FORM_ELEMENTS[key]
58
+ version = meta[:archived_in_version] || meta[:removed_in_version]
59
+ hint = if meta[:archive_path]
60
+ "Restore from #{meta[:archive_path]}/README.md or vendor into your app."
61
+ else
62
+ "See archived/README.md and CHANGELOG #{version}."
63
+ end
64
+
65
+ raise ArchivedFormElementError,
66
+ "#{klass.name} inline_forms_attribute_list declares #{attribute}:#{form_element}, " \
67
+ "which was retired in inline_forms #{version} (#{meta[:summary]}). #{hint}"
68
+ end
69
+ end
70
+ end
@@ -1,4 +1,4 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module InlineForms
3
- VERSION = "7.2.11"
3
+ VERSION = "7.9.1"
4
4
  end
data/lib/inline_forms.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  require ('inline_forms/version.rb')
3
3
  require_relative ('inline_forms/form_element_from_callee')
4
+ require_relative ('inline_forms/archived_form_elements')
4
5
  # InlineForms is a Rails Engine that let you setup an admin interface quick and
5
6
  # easy. Please install it as a gem or include it in your Gemfile.
6
7
  module InlineForms
8
+ class PlainTextColumnMissingError < StandardError; end
9
+
7
10
  # DEFAULT_COLUMN_TYPES holds the standard ActiveRecord::Migration column types.
8
11
  # This list provides compatability with the standard types, but we add our own
9
12
  # later in 'Special Column Types'.
@@ -62,7 +65,7 @@ module InlineForms
62
65
  #
63
66
  DEFAULT_FORM_ELEMENTS = {
64
67
  :string => :text_field,
65
- :text => :text_area,
68
+ :text => :plain_text,
66
69
  :integer => :text_field,
67
70
  :float => :text_field,
68
71
  :decimal => :text_field,
@@ -93,6 +96,45 @@ module InlineForms
93
96
  :associated => :no_migration
94
97
  }
95
98
 
99
+ PLAIN_TEXT_FORM_ELEMENTS = %i[
100
+ plain_text
101
+ plain_text_area
102
+ text_area_without_ckeditor
103
+ ].freeze
104
+
105
+ def self.plain_text_form_element?(form_element)
106
+ PLAIN_TEXT_FORM_ELEMENTS.include?(form_element.to_sym)
107
+ rescue NoMethodError
108
+ false
109
+ end
110
+
111
+ def self.assert_plain_text_column!(object:, attribute:, form_element:)
112
+ return unless plain_text_form_element?(form_element)
113
+ return if object.class.column_names.include?(attribute.to_s)
114
+
115
+ raise PlainTextColumnMissingError,
116
+ "#{object.class.name}##{attribute} uses #{form_element} but has no DB column `#{attribute}`. " \
117
+ "Use :rich_text for ActionText-backed attributes, or add a text column for :plain_text."
118
+ end
119
+
120
+ def self.validate_plain_text_configuration_for!(klass)
121
+ return unless klass.respond_to?(:table_exists?) &&
122
+ klass.respond_to?(:column_names) &&
123
+ klass.instance_methods.include?(:inline_forms_attribute_list)
124
+ return unless klass.table_exists?
125
+
126
+ attributes = klass.new.inline_forms_attribute_list
127
+ attributes.each do |attribute, _label, form_element|
128
+ next unless plain_text_form_element?(form_element)
129
+ next if klass.column_names.include?(attribute.to_s)
130
+
131
+ raise PlainTextColumnMissingError,
132
+ "#{klass.name} inline_forms_attribute_list declares #{attribute}:#{form_element}, " \
133
+ "but table `#{klass.table_name}` has no `#{attribute}` column. " \
134
+ "Use :rich_text for ActionText-backed attributes."
135
+ end
136
+ end
137
+
96
138
  # RELATIONS defines a mapping between AR::Migrations columns and the Model.
97
139
  #
98
140
  # When a column has the type of :references or :belongs_to, then
@@ -144,7 +186,6 @@ module InlineForms
144
186
  inline_forms/inline_forms.css
145
187
  inline_forms/devise.css
146
188
  inline_forms/inline_forms.js
147
- inline_forms/ckeditor/config.js
148
189
  inline_forms/glass_plate.gif
149
190
  )
150
191
  end
@@ -159,6 +200,23 @@ module InlineForms
159
200
  app.config.assets.paths << path unless app.config.assets.paths.include?(path)
160
201
  end
161
202
 
203
+ config.to_prepare do
204
+ next unless defined?(ActiveRecord::Base)
205
+
206
+ ActiveRecord::Base.descendants.each do |klass|
207
+ begin
208
+ InlineForms.validate_no_archived_form_elements_for!(klass)
209
+ InlineForms.validate_plain_text_configuration_for!(klass)
210
+ rescue InlineForms::PlainTextColumnMissingError, InlineForms::ArchivedFormElementError
211
+ raise
212
+ rescue StandardError
213
+ # Some descendants might be abstract or temporarily unresolved while
214
+ # the app boots/reloads; runtime checks in controllers still enforce
215
+ # plain_text column presence for active resources.
216
+ end
217
+ end
218
+ end
219
+
162
220
  I18n.load_path << Dir[File.join(File.expand_path(File.dirname(__FILE__) + '/locales'), '*.yml')]
163
221
  I18n.load_path.flatten!
164
222
  end