inline_forms 7.2.11 → 7.5.2

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +176 -0
  3. data/README.rdoc +4 -2
  4. data/app/assets/javascripts/inline_forms/inline_forms.js +23 -0
  5. data/app/assets/stylesheets/inline_forms/inline_forms.scss +32 -16
  6. data/app/controllers/concerns/versions_concern.rb +2 -1
  7. data/app/controllers/inline_forms_application_controller.rb +5 -1
  8. data/app/controllers/inline_forms_controller.rb +120 -24
  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 +127 -71
  16. data/app/views/devise/sessions/_form.html.erb +4 -1
  17. data/app/views/inline_forms/_close.html.erb +6 -4
  18. data/app/views/inline_forms/_edit.html.erb +7 -40
  19. data/app/views/inline_forms/_list.html.erb +52 -39
  20. data/app/views/inline_forms/_new.html.erb +23 -11
  21. data/app/views/inline_forms/_show.html.erb +13 -11
  22. data/app/views/inline_forms/_versions_list.html.erb +4 -8
  23. data/app/views/inline_forms/create_list_frame.html.erb +3 -0
  24. data/app/views/inline_forms/field_edit.html.erb +3 -0
  25. data/app/views/inline_forms/field_show.html.erb +3 -0
  26. data/app/views/inline_forms/new_record.html.erb +3 -0
  27. data/app/views/inline_forms/row_close.html.erb +5 -0
  28. data/app/views/inline_forms/row_destroyed.html.erb +9 -0
  29. data/app/views/inline_forms/row_show.html.erb +3 -0
  30. data/app/views/inline_forms/versions_list_panel.html.erb +3 -0
  31. data/app/views/inline_forms/versions_panel.html.erb +3 -0
  32. data/app/views/layouts/application.html.erb +0 -1
  33. data/app/views/layouts/inline_forms.html.erb +10 -1
  34. data/bin/inline_forms +22 -1
  35. data/bin/inline_forms_installer_core.rb +38 -3
  36. data/docs/ujs-to-turbo.md +193 -0
  37. data/lib/generators/USAGE +2 -2
  38. data/lib/generators/assets/stylesheets/inline_forms.scss +32 -16
  39. data/lib/inline_forms/version.rb +1 -1
  40. data/lib/inline_forms.rb +58 -2
  41. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_field_turbo_test.rb +74 -0
  42. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_list_test.rb +73 -0
  43. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_photos_pagination_test.rb +199 -15
  44. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_row_turbo_test.rb +94 -0
  45. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_top_level_new_test.rb +100 -0
  46. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_versions_turbo_test.rb +27 -0
  47. data/lib/installer_templates/example_app_tests/test/models/example_app_plain_text_rich_text_edge_cases_test.rb +46 -0
  48. data/lib/installer_templates/example_app_views/apartments/name_list.html.erb +26 -0
  49. data/lib/installer_templates/example_app_views/inline_forms/_header.html.erb +45 -0
  50. data/test/inline_forms_generator_test.rb +10 -0
  51. data/test/plain_text_configuration_test.rb +90 -0
  52. metadata +21 -5
  53. data/app/views/inline_forms/edit.js.erb +0 -1
  54. data/app/views/inline_forms/show_element.js.erb +0 -1
  55. data/app/views/inline_forms/update.js.erb +0 -1
  56. data/lib/generators/assets/javascripts/ckeditor/config.js +0 -72
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc1f19aa65a4c6d91b8a566e42e1a397257de689d2bf0f47ea8b68ce7a27e597
4
- data.tar.gz: 15ed1d356f42fca6087414b60a292bb2fffa5fb87d1f55f2bfbec0cf78408ad8
3
+ metadata.gz: bbf211e9092af732b645bf10e9f9c338ced10c55e3ebf0a533146537669d1a4d
4
+ data.tar.gz: 90bc8cf55083ca23bbc1d3122a49e835620bf2e4800c8c83a93b871230d54718
5
5
  SHA512:
6
- metadata.gz: 2f7badd154eccacf2c727f2ed13624da92352fee6056a12f8bf8d5f43287283d3270208b82200eb7f4c2b68b0356b0bc11583718e0fdc754e96c43520580a494
7
- data.tar.gz: 444994d5d929591ba1f2e1cd8697edbd078e945e48772ab574b5797e4c0cd64cea1e9db69644dc58803a385750d085d140bee45611727f45b3f12bd8e9d81b33
6
+ metadata.gz: 5f66995db3089c59e1654f92f1a7c78b9701ccad3ea0a12fb4cf7729ea5ed1bff70003847af80ebcae6347c05e29f7e6fc9e8a9317252ea120a7a09d790c2284
7
+ data.tar.gz: 2e04d9869aff6e13a1eb6556e9865e640725dee6d9ad5a56887ed23f125bd9378ab679c773fd84cbc72b8df408335dd7026998e113254366e4cc358155c70785
data/CHANGELOG.md CHANGED
@@ -4,6 +4,182 @@ All notable changes to this project are documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [7.5.2] - 2026-05-16
8
+
9
+ ### Fixed
10
+
11
+ - **Field re-edit after Turbo update / cancel (Photo image, name, etc.):** `render_turbo_field` now sets **`@inline_forms_turbo_field = true`**, so the link inside the swapped **`<turbo-frame id="photo_<id>_image">`** carries **`data-turbo`** instead of legacy **`data-remote`**. 7.5.1 set the flag only in `_show.html.erb` (the row open template); the bare `field_show` re-render after a field cancel/update lost it, the link fell back to **`remote: true`**, jquery_ujs intercepted as a JS request the controller does not register, and the second click silently failed (no swap, no edit form).
12
+ - **Top-level `+ new` flow (e.g. /apartments):** `link_to_new_record` falls back to **UJS (`remote: true`)** when called without a `parent_class`. The top-level list root stays a **`<div id="apartments_list">`** (a `<turbo-frame>` there collapses inside `position: absolute` `#outer_container` and hides rows under the fixed top bar), so a Turbo target on the **`+`** link is invalid -- 7.5.1 emitted `data-turbo-frame="apartments_list"`, which made cancel/create produce Turbo's "Content missing" or fall back to a full-page navigation. UJS (`new.js.erb` / `list.js.erb`) swap **`#apartments_list`** in place; nested has_many lists keep the Turbo contract unchanged.
13
+
14
+ ### Added
15
+
16
+ - **Defensive CSS:** `turbo-frame { display: block; }` so any `<turbo-frame>` inside `#outer_container` (custom elements default to `display: inline`) does not collapse and hide its row content.
17
+ - **Regression tests:**
18
+ - `example_app_apartment_top_level_new_test.rb` -- top-level list root stays `<div>`, **`+`** link is UJS, `new` / `cancel` / `create` go through `format.js` and swap **`#apartments_list`**.
19
+ - `example_app_apartment_photos_pagination_test.rb` -- after Turbo update / cancel of a Photo image field, `field_show` carries `data-turbo="true"` (not `data-remote="true"`).
20
+
21
+ ### Verified
22
+
23
+ - **`bundle exec rails test`** -- **60 runs, 302 assertions, 0 failures**.
24
+ - **curl:** field show after cancel/update emits `data-turbo="true" data-turbo-frame="_self"` (no `data-remote`); top-level new returns `format.js` swap of `#apartments_list`.
25
+ - **Browser:** top-level `+ new Apartment` form renders inline + cancel returns to list (no "Content missing"); nested Photo image edit can be reopened immediately after cancel and after replacement.
26
+
27
+ ## [7.5.1] - 2026-05-16
28
+
29
+ ### Fixed
30
+
31
+ - **Nested associated `+` (New) on `not_accessible_through_html?` models (Photo):** `new` now serves **`format.html`** when `parent_class` and `update` are present (was **406** / empty frame for Photo).
32
+ - **New / Cancel / OK after New:** associated-list frame responses use the **`inline_forms`** layout (styled) instead of bare **`turbo_rails/frame`**; **`create`** restores the list with **`@ul_needed = true`** so the inner **`…_photos_list`** `<turbo-frame>` matches **`_show`** (fixes Turbo **“Content missing”** on cancel and create).
33
+ - **`_new.html.erb`:** Turbo form sets **`data-turbo-frame`** to the parent list frame id.
34
+
35
+ ### Verified
36
+
37
+ - **`bundle exec rails test`** — **53 runs, 260 assertions, 0 failures**; nested Photo new → cancel → create integration test.
38
+ - **curl:** new **200** + stylesheet; cancel/create **200** with **`apartment_<id>_photos`** + **`…_photos_list`** frames.
39
+
40
+ ## [7.5.0] - 2026-05-16
41
+
42
+ ### Added
43
+
44
+ - **Step 3 completion (UJS → Turbo row/field lifecycle):** row toolbar **`soft_delete` / `soft_restore` / `destroy` / `revert`** respond with **`format.html`** (`row_close`, `row_destroyed`) inside matching **`<turbo-frame>`**; helpers default to Turbo (`inline_forms_turbo_link_data`, `turbo_row:` on toolbar / versions / nested **`+`** links).
45
+ - **Nested `+new` / `create` / versions panel:** `new_record`, `create_list_frame`, `versions_panel`, `versions_list_panel` HTML templates; associated/has_one/versions regions in **`_show`** wrapped in **`<turbo-frame>`**.
46
+ - **`turbo:frame-load`** in **`inline_forms.js`:** re-init datepicker, timepicker, and Trix after frame swaps.
47
+ - **Regression tests:** row destroy/revert Turbo, versions panel Turbo (`example_app_apartment_row_turbo_test.rb`, `example_app_apartment_versions_turbo_test.rb`).
48
+
49
+ ### Removed
50
+
51
+ - **`edit.js.erb`**, **`update.js.erb`**, **`show_element.js.erb`** — scalar field edit/update/cancel is Turbo HTML only.
52
+
53
+ ### Changed
54
+
55
+ - **`docs/ujs-to-turbo.md`:** Step 3 checklist marked done except **`show.js.erb` / `close.js.erb` / …** retained for **`_tree.html.erb`** (Step 4).
56
+
57
+ ### Verified (end-to-end against the `--example` install)
58
+
59
+ - **`bundle exec rails test`** — **52 runs, 236 assertions, 0 failures, 0 errors, 0 skips** against the generated MyApp.
60
+ - **curl smoke:** versions panel **`GET /apartments/1/list_versions?update=apartment_1_versions`** with **`Turbo-Frame`** → **200** + matching **`<turbo-frame id="apartment_1_versions">`**.
61
+ - **curl + browser** — row toolbar, versions, field edit, nested photos (same contract as 7.4.x).
62
+
63
+ ## [7.4.5] - 2026-05-15
64
+
65
+ ### Added
66
+
67
+ - **Example app integration test** (`example_app_apartment_photos_pagination_test.rb`): nested Photo **`image`** field — Turbo **`Turbo-Frame`** GET edit (multipart form) and **multipart `PUT`** — asserts **200** HTML with matching **`<turbo-frame id="…_image">`** and no **`UnknownFormat`** / **406** (Step 3 multipart regression guard from the 7.2.0 nested-frame era).
68
+
69
+ ### Changed
70
+
71
+ - **`docs/ujs-to-turbo.md`:** Step 3 checklist — “replace photo image (multipart) inside nested frame” marked done.
72
+
73
+ ### Verified (end-to-end against the `--example` install)
74
+
75
+ - **`bundle exec rails test`** — **47 runs, 208 assertions, 0 failures, 0 errors, 0 skips** against the generated MyApp (Apartment + Photo).
76
+ - **curl smoke** (authenticated **`Turbo-Frame`** GETs / **`POST` multipart** with **`_method=put`**): same contract as 7.4.4 plus nested Photo **`/photos/1/edit?attribute=image&form_element=image_field&update=apartment_1_photo_<id>_image`** and image replace **`POST /photos/1?...`** — **200** and matching **`<turbo-frame id="…_image">`** (Devise scope **`/auth/users/sign_in`**; cookie jar must be sent on the multipart step).
77
+ - **Browser** (cursor-ide-browser MCP, dev server): already-signed-in session — open **Konferensha** row (URL stays **`/`**, gallery appears) — **Next page** (gallery shows page-2 filenames) — **`/apartments/name_list`** — edit name — **ok** (read-only link shows new value).
78
+
79
+ ## [7.4.4] - 2026-05-16
80
+
81
+ ### Fixed
82
+
83
+ - **`inline_forms create … --example` against a Rails 8 system gem:** `bin/inline_forms` now prefers a locally installed Rails **`~> 7.0`** (`rails _7.0.X_ new …`) when one is present, so the generated **`config/application.rb`** matches the **`rails ~> 7.0.0`** pin the installer writes into the **`Gemfile`**. Without this, a system **`rails 8.x`** wrote **`config.load_defaults 8.0`** and **`config.autoload_lib(ignore: …)`** into `application.rb`, both rejected by Rails 7.0 (`rails aborted! Unknown version "8.0"` and `NoMethodError: undefined method 'autoload_lib'` on the first **`bundle exec rails dartsass:install`**), so app generation aborted right after Dart Sass install.
84
+ - **Defensive `application.rb` rewrite (belt + suspenders):** even when the picked generator is Rails 8.x (e.g. no 7.0 gem available), `bin/inline_forms_installer_core.rb` rewrites **`config.load_defaults <N>.<M>`** to **`config.load_defaults 7.0`** and strips **`config.autoload_lib(…)`** post-generation so the bundled Rails 7.0 can boot.
85
+
86
+ ### Changed
87
+
88
+ - **`docs/ujs-to-turbo.md`:** Step 3 marks the apartment field flow integration test (**`example_app_apartment_field_turbo_test.rb`** — open row → edit text field → save → cancel) as done; the test has been shipping since 7.4.1.
89
+
90
+ ### Verified (end-to-end against the `--example` install)
91
+
92
+ - **`bundle exec rails test`** — **46 runs, 196 assertions, 0 failures, 0 errors, 0 skips** against the generated MyApp (Apartment + Photo).
93
+ - **curl smoke** with **`Turbo-Frame`** header: top-level row open/close (**`/apartments/1?update=apartment_1[&close=true]`**), scalar field edit/update/cancel (**`/apartments/1/edit?attribute=name&form_element=text_field&update=apartment_1_name`** + **`PUT /apartments/1`**), nested **`/photos?parent_class=Apartment&parent_id=1&update=apartment_1_photos_list`** pagination, and nested Photo row open/close (**`/photos/1?update=apartment_1_photo_1[&close=true]`**) all return **200** with the matching **`<turbo-frame id="…">`** in the body.
94
+ - **Browser** (devtools MCP, Drive disabled, UJS for the legacy paths): sign-in → click apartment row (Turbo open, no full-page nav) → nested photo pagination Next (Turbo) → click name field (Turbo edit-in-place) → save (in-place swap, no reload) → close X (Turbo collapse, list shows updated name). **`/apartments/name_list`** field-edit demo confirmed reachable from the **More** menu.
95
+
96
+ ## [7.4.3] - 2026-05-16
97
+
98
+ ### Added
99
+
100
+ - **Turbo row open/close on stock index:** each top-level list row is a **`<turbo-frame id="{model}_{id}">`**; presentation links use Turbo (**`row_show.html.erb`** / **`row_close.html.erb`**, **`close_link(..., turbo_row:)`**, **`example_app_apartment_row_turbo_test.rb`**).
101
+
102
+ ### Fixed
103
+
104
+ - **Nested associated list (e.g. Apartment → Photo):** same per-row **`<turbo-frame>`** + Turbo presentation as top-level; removed **`data-turbo="false"`**, which broke nested **cancel** and other in-frame GETs. **`row_html_turbo_allowed?`** / **`nested_associated_list_row_update?`** serve **`format.html`** row **show** / **close** for **`not_accessible_through_html?`** models when **`params[:update]`** is a nested row id (e.g. **`apartment_1_photo_5`**).
105
+
106
+ ### Changed
107
+
108
+ - **`docs/ujs-to-turbo.md`:** Step 2 / Step 3 checklist for row + nested Turbo.
109
+ - **`example_app_apartment_photos_pagination_test.rb`:** asserts nested turbo-frame rows and Photo row open/close + field cancel; drops the obsolete requirement that nested rows opt out of Turbo.
110
+
111
+ ## [7.4.1] - 2026-05-15
112
+
113
+ ### Added
114
+
115
+ - **Turbo field edit (Step 3, partial):** scalar fields in stock **`_show.html.erb`** are wrapped in **`<turbo-frame id="{model}_{id}_{attribute}">`**. Field **edit**, **update**, and **cancel** respond with **`format.html`** via **`field_edit.html.erb`** / **`field_show.html.erb`** and the **`turbo_rails/frame`** layout (no UJS on field links/forms). Row-level show/close remains UJS.
116
+ - **`inline_forms_field_cancel_link`** helper and **`inline_forms_field_show`** helper; **`link_to_inline_edit`** accepts **`turbo_frame:`** and omits **`remote: true`** on the Turbo path.
117
+ - **Regression tests** **`example_app_apartment_field_turbo_test.rb`** (stock panel field turbo-frame edit/update/cancel) and extended **`example_app_apartment_name_list_test.rb`** (turbo-frame contract, no **`UnknownFormat`** on cancel).
118
+
119
+ ### Fixed
120
+
121
+ - **Field cancel on Turbo path:** cancel no longer triggers full-page navigation or **`UnknownFormat`** — **`format.html`** is always registered for single-attribute show; cancel uses a plain GET link with **`data-turbo-frame="_self"`** (no **`data-method`**, which conflicted with jQuery UJS).
122
+ - **Cancel button height:** restored **`input[type=button]`** inside a thin wrapper link (Turbo/UJS attrs on the **`<a>`**) so cancel matches the **ok** submit height; Foundation **`a.button`** was rendering much taller in collapse rows.
123
+
124
+ ### Changed
125
+
126
+ - **Example name list (`--example`):** uses the same turbo-field contract as stock **`_show`** ( **`@inline_forms_turbo_field`**, **`<turbo-frame>`** wrappers), not a separate UJS path.
127
+ - **`docs/ujs-to-turbo.md`:** Step 3 field-level checklist items marked done for stock scalar fields and name-list regression.
128
+
129
+ ## [7.4.0] - 2026-05-15
130
+
131
+ ### Added
132
+
133
+ - **`--example` demo: field-level inline edit without the stock `_show` UI** (`ApartmentsController#name_list`, **`GET /apartments/name_list`**). Lists the first 10 apartments with each **`name`** rendered via **`text_field_show`** inside a wrapper `id="apartment_<id>_name"`, so edit/update use the normal polymorphic paths without opening the full inline-edit panel. Installer copies **`app/views/apartments/name_list.html.erb`**, injects the controller action (with CanCan **`skip_load_and_authorize_resource`** / **`authorize! :read, Apartment`**), and adds the route.
134
+ - **More menu link** in the example app only: installer overrides **`app/views/inline_forms/_header.html.erb`** with an extra item **“Apartment names (first 10)”** pointing at **`apartment_name_list_path`**.
135
+ - **UJS → Turbo migration checklist** at **`docs/ujs-to-turbo.md`** (Steps 1–2 done, 3–5 tracked).
136
+ - **Regression test** **`test/integration/example_app_apartment_name_list_test.rb`**: page render, More menu link, and UJS edit-link **`update=`** contract.
137
+
138
+ ## [7.3.4] - 2026-05-15
139
+
140
+ ### Changed
141
+
142
+ - **Installer Gemfile**: `devise-i18n` is taken from RubyGems (`~> 1.16`, current release **1.16.0**) instead of the obsolete `https://github.com/acesuares/devise-i18n.git` fork (which matched upstream only through 2018). `devise` is pinned to **`~> 5.0`** so it satisfies `devise-i18n` 1.16’s runtime dependency on Devise 5+.
143
+
144
+ ## [7.3.3] - 2026-05-07
145
+
146
+ ### Fixed
147
+
148
+ - **Session flash no longer appears inside inline-edit fields**: `_edit.html.erb` was injected into the field span via UJS and iterated the full `flash` hash, so an unconsumed Devise notice (for example after sign-in) could show up inside the Trix/rich-text editor when opening a field. The edit partial no longer renders global flash; `_new.html.erb` only shows `flash.now` keys used by `create` (`header`, `error`, `success`). **`layouts/inline_forms.html.erb`** now renders **`flash["notice"]` / `flash["alert"]`** under the header so redirect flash is shown once and consumed on normal pages.
149
+ - **`InlineFormsApplicationController` default layout**: `layout 'devise' if :devise_controller?` was ineffective because `:devise_controller?` is a Symbol (always truthy), so every controller defaulted to `layouts/devise`. Replaced with `layout ->(c) { c.devise_controller? ? "devise" : "inline_forms" }` so app controllers use the full **`inline_forms`** chrome unless an action overrides `render` layout.
150
+ - **Devise sign-in form** (`app/views/devise/sessions/_form.html.erb`): added `data-turbo="false"` on the form so a Turbo-enabled asset bundle cannot turn the POST into a non-navigational request (which would skip Devise’s `set_flash_message!` for `:signed_in`).
151
+
152
+ ## [7.3.2] - 2026-05-07
153
+
154
+ ### Fixed
155
+
156
+ - **Legacy `:text_area` alias no longer triggers plain-text column checks**: `:text_area` is treated as the rich-text alias path, while plain-text column enforcement remains limited to `:plain_text`, `:plain_text_area`, and `:text_area_without_ckeditor`. This prevents false `InlineForms::PlainTextColumnMissingError` when switching an ActionText-backed attribute from `:rich_text` to `:text_area`.
157
+
158
+ ## [7.3.1] - 2026-05-07
159
+
160
+ ### Changed
161
+
162
+ - **Long text form element naming is now explicit**: `:plain_text` is the canonical non-WYSIWYG textarea form element (backed by a DB `text` column), while `:rich_text` remains the ActionText/Trix element.
163
+ - **Default mapping for migration type `:text` now emits form element `:plain_text`** (instead of `:text_area`) so newly generated models use the explicit name.
164
+ - **Legacy aliases remain supported**: `:text_area_without_ckeditor` and `:plain_text_area` delegate to `:plain_text`; legacy `:text_area` and `:ckeditor` delegate to `:rich_text`.
165
+
166
+ ### Fixed
167
+
168
+ - **Misconfigured `plain_text` attributes now fail fast with a clear error**: inline_forms now raises `InlineForms::PlainTextColumnMissingError` when a `plain_text`-style form element targets an attribute without a DB column (for example, an ActionText-only attribute such as `description` after switching from `:rich_text` to `:text_area`/`:plain_text` without adding a column).
169
+ - **Checks run both during app boot/reload (for loaded models) and at request runtime** (`getKlass`, `create`, `update`) to prevent late `ActiveModel::MissingAttributeError` failures.
170
+ - **Generated example-app tests now cover rich_text/plain_text edge cases**, including the failure path above and the reverse direction (`plain_text` -> `rich_text`) staying non-failing from inline_forms' perspective.
171
+
172
+ ## [7.3.0] - 2026-05-07
173
+
174
+ ### Removed
175
+
176
+ - **CKEditor**: no CDN script tags in engine layouts, no `cktext_area_tag` / `CKEDITOR` usage, no `.ckeditor_area` styles, and no `inline_forms/ckeditor/config.js` asset precompile entry. Long text uses the same plain `<textarea>` path as before when the CKEditor gem was absent.
177
+
178
+ ### Changed
179
+
180
+ - **`:text_area`** always renders a plain multiline field (equivalent to the old non-CKEditor path and to **`:text_area_without_ckeditor`**).
181
+ - **`:ckeditor`** remains a valid generator/model type name but now delegates to **`:text_area`** behavior (plain text); migrate to **`:text_area`** or **`:rich_text`** when convenient.
182
+
7
183
  ## [7.2.11] - 2026-05-07
8
184
 
9
185
  ### Changed
data/README.rdoc CHANGED
@@ -30,9 +30,9 @@ You can install the example application manually if you like:
30
30
 
31
31
  inline_forms create MyApp
32
32
  cd MyApp
33
- rails g inline_forms Picture name:string caption:string image:image_field description:text apartment:belongs_to _presentation:'#{name}'
33
+ rails g inline_forms Picture name:string caption:string image:image_field description:plain_text apartment:belongs_to _presentation:'#{name}'
34
34
  rails generate uploader Image
35
- rails g inline_forms Apartment name:string title:string description:text pictures:has_many pictures:associated _enabled:yes _presentation:'#{name}'
35
+ rails g inline_forms Apartment name:string title:string description:rich_text pictures:has_many pictures:associated _enabled:yes _presentation:'#{name}'
36
36
  bundle exec rake db:migrate
37
37
  rails s
38
38
 
@@ -48,6 +48,8 @@ The +:image_field+ form element uses CarrierWave. Generated apps depend on +carr
48
48
 
49
49
  To switch to S3, add +carrierwave-aws+ (or use the bundled fog backend) and configure a +CarrierWave.configure+ block in +config/initializers/carrierwave.rb+; nothing in inline_forms hard-codes local storage.
50
50
 
51
+ For long text fields, use +:plain_text+ for a plain textarea backed by a DB +text+ column, or +:rich_text+ for ActionText/Trix content. +:plain_text+ requires an actual column on the model table; if the column is missing, inline_forms now raises +InlineForms::PlainTextColumnMissingError+ during controller boot/runtime checks.
52
+
51
53
  Note: generated apps also depend on ActiveStorage transitively because the +:rich_text+ form element uses ActionText (+active_storage:install+ runs during +inline_forms create+). Image uploads still go through CarrierWave; ActiveStorage is only there to back ActionText embeds.
52
54
 
53
55
  = Build a vagrant virtualbox box for easier development
@@ -37,3 +37,26 @@ $(function(){ $(document).foundation(); });
37
37
  $(this).attr('title', '');
38
38
  });
39
39
  });
40
+
41
+ // Re-bind jQuery UI widgets and Trix after Turbo Frame swaps (Step 3).
42
+ document.addEventListener("turbo:frame-load", function(event) {
43
+ var root = event.target;
44
+ if (!root || !root.querySelectorAll) { return; }
45
+
46
+ $(root).find("input.datepicker").each(function() {
47
+ var $el = $(this);
48
+ if (!$el.hasClass("hasDatepicker")) { $el.datepicker(); }
49
+ });
50
+
51
+ $(root).find("input.timepicker").each(function() {
52
+ var $el = $(this);
53
+ if (!$el.data("timepicker")) { $el.timepicker(); }
54
+ });
55
+
56
+ $(root).find("trix-editor").each(function() {
57
+ if (window.Trix && this.editor) { return; }
58
+ if (window.Trix && typeof Trix.Editor === "function") {
59
+ new Trix.Editor(this);
60
+ }
61
+ });
62
+ });
@@ -223,6 +223,14 @@ 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 an empty/just-mounted top-level `<turbo-frame id="apartments_list">` and
228
+ // hides its rows under the fixed top bar. Force block layout so frames behave
229
+ // like the legacy `<div class="list_container">` and `<div class="row">` they
230
+ // replaced (see app/views/inline_forms/_list.html.erb).
231
+ turbo-frame {
232
+ display: block;
233
+ }
226
234
  .list_container {
227
235
  .row {
228
236
  font-size: 1.2rem;
@@ -258,6 +266,30 @@ select:hover, select:focus {
258
266
  }
259
267
  }
260
268
 
269
+ // Field edit cancel: outer <a> carries Turbo/UJS; inner input[type=button] matches ok height.
270
+ .edit_form .row.collapse {
271
+ input[type="submit"].postfix.button,
272
+ a.inline_forms-field-cancel input[type="button"].postfix.button {
273
+ margin: 2px 0 !important;
274
+ }
275
+ }
276
+
277
+ .edit_form a.inline_forms-field-cancel {
278
+ display: inline-block;
279
+ padding: 0;
280
+ border: 0;
281
+ background: transparent;
282
+ line-height: 0;
283
+ vertical-align: top;
284
+ text-decoration: none;
285
+
286
+ input[type="button"] {
287
+ pointer-events: none;
288
+ cursor: pointer;
289
+ width: auto;
290
+ }
291
+ }
292
+
261
293
  .object_presentation {
262
294
  background-color: #B94C32;
263
295
  color: white;
@@ -434,22 +466,6 @@ select:hover, select:focus {
434
466
  margin-bottom: 0.5em;
435
467
  }
436
468
 
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
469
  /* jQuery ui Slider 8 */
454
470
  .slider {
455
471
  width: 300px;
@@ -11,11 +11,12 @@ module VersionsConcern
11
11
  close = params[:close] || false
12
12
  if close
13
13
  respond_to do |format|
14
+ format.html { render "inline_forms/versions_panel", layout: "turbo_rails/frame" }
14
15
  format.js { render :versions }
15
16
  end
16
17
  else
17
18
  respond_to do |format|
18
- format.html { } unless @Klass.not_accessible_through_html?
19
+ format.html { render "inline_forms/versions_list_panel", layout: "turbo_rails/frame" }
19
20
  format.js { render :versions_list }
20
21
  end
21
22
  end
@@ -1,7 +1,11 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  class InlineFormsApplicationController < ActionController::Base
3
3
  protect_from_forgery
4
- layout 'devise' if :devise_controller?
4
+ # `layout 'devise' if :devise_controller?` was wrong: `:devise_controller?` is a
5
+ # Symbol (always truthy), so every controller used the Devise layout. Use a
6
+ # callable so only Devise controllers get `layouts/devise`; everything else
7
+ # defaults to `layouts/inline_forms` (actions may still override via `render`).
8
+ layout ->(controller) { controller.devise_controller? ? "devise" : "inline_forms" }
5
9
 
6
10
  # limit available locales by setting this. Override in applicaton_controller.
7
11
  I18n.available_locales = [ :en, :nl, :pp ]
@@ -72,12 +72,17 @@ class InlineFormsController < ApplicationController
72
72
  if @Klass.not_accessible_through_html?
73
73
  format.html do
74
74
  if @parent_class.present?
75
- frame_layout = turbo_frame_request? ? "turbo_rails/frame" : "inline_forms"
76
- render "inline_forms/_list", layout: frame_layout
75
+ render_nested_associated_list_html
77
76
  end
78
77
  end
79
78
  else
80
- format.html { render 'inline_forms/_list', :layout => 'inline_forms' }
79
+ format.html do
80
+ if @parent_class.present?
81
+ render_nested_associated_list_html
82
+ else
83
+ render "inline_forms/_list", layout: "inline_forms"
84
+ end
85
+ end
81
86
  end
82
87
  format.js { render :list }
83
88
  end
@@ -98,7 +103,7 @@ class InlineFormsController < ApplicationController
98
103
 
99
104
  @object.inline_forms_attribute_list = @inline_forms_attribute_list if @inline_forms_attribute_list
100
105
  respond_to do |format|
101
- format.html { render 'inline_forms/_new', :layout => 'inline_forms' } unless @Klass.not_accessible_through_html?
106
+ format.html { render_turbo_new } if associated_list_html_allowed? || !@Klass.not_accessible_through_html?
102
107
  format.js { }
103
108
  end
104
109
  end
@@ -111,8 +116,7 @@ class InlineFormsController < ApplicationController
111
116
  @sub_id = params[:sub_id]
112
117
  @update_span = params[:update]
113
118
  respond_to do |format|
114
- format.html { } unless @Klass.not_accessible_through_html?
115
- format.js { }
119
+ format.html { render_turbo_field(:field_edit) }
116
120
  end
117
121
  end
118
122
 
@@ -123,6 +127,7 @@ class InlineFormsController < ApplicationController
123
127
  @update_span = params[:update]
124
128
  attributes = @inline_forms_attribute_list || @object.inline_forms_attribute_list
125
129
  attributes.each do | attribute, name, form_element |
130
+ InlineForms.assert_plain_text_column!(object: @object, attribute: attribute, form_element: form_element)
126
131
  send("#{form_element.to_s}_update", @object, attribute) unless form_element == :tree || form_element == :associated || (cancan_enabled? && cannot?(:read, @object, attribute))
127
132
  end
128
133
  @parent_class = params[:parent_class]
@@ -145,14 +150,16 @@ class InlineFormsController < ApplicationController
145
150
  @objects = @objects.where(conditions).paginate(:page => params[:page])
146
151
  @object = nil
147
152
  respond_to do |format|
148
- format.js { render :list}
153
+ format.html { render_associated_list_frame } if associated_list_html_allowed?
154
+ format.js { render :list }
149
155
  end
150
156
  else
151
157
  flash.now[:header] = ["Kan #{@object.class.to_s.underscore} niet aanmaken."]
152
158
  flash.now[:error] = @object.errors.to_a
153
159
  respond_to do |format|
154
160
  @object.inline_forms_attribute_list = attributes
155
- format.js { render :new}
161
+ format.html { render_turbo_new } if associated_list_html_allowed? || !@Klass.not_accessible_through_html?
162
+ format.js { render :new }
156
163
  end
157
164
  end
158
165
  end
@@ -164,11 +171,11 @@ class InlineFormsController < ApplicationController
164
171
  @form_element = params[:form_element]
165
172
  @sub_id = params[:sub_id]
166
173
  @update_span = params[:update]
174
+ InlineForms.assert_plain_text_column!(object: @object, attribute: @attribute, form_element: @form_element)
167
175
  send("#{@form_element.to_s}_update", @object, @attribute)
168
176
  @object.save
169
177
  respond_to do |format|
170
- format.html { } unless @Klass.not_accessible_through_html?
171
- format.js { }
178
+ format.html { render_turbo_field(:field_show, turbo_field_show: true) }
172
179
  end
173
180
  end
174
181
 
@@ -195,15 +202,16 @@ class InlineFormsController < ApplicationController
195
202
  respond_to do |format|
196
203
  @attributes = @object.inline_forms_attribute_list
197
204
  if close
205
+ format.html { render_row_turbo(:close) } if row_html_turbo_allowed?
198
206
  format.js { render :close }
199
207
  else
200
- format.js { }
208
+ format.html { render_row_turbo(:show) } if row_html_turbo_allowed?
209
+ format.js { render :show }
201
210
  end
202
211
  end
203
212
  else
204
213
  respond_to do |format|
205
- format.html { } unless @Klass.not_accessible_through_html?
206
- format.js { render :show_element }
214
+ format.html { render_turbo_field(:field_show, turbo_field_show: true) }
207
215
  end
208
216
  end
209
217
  end
@@ -212,10 +220,9 @@ class InlineFormsController < ApplicationController
212
220
  def soft_delete
213
221
  @update_span = params[:update]
214
222
  @object = referenced_object
215
- # destroy the object
216
223
  @object.soft_delete(current_user)
217
224
  respond_to do |format|
218
- format.html { } unless @Klass.not_accessible_through_html?
225
+ format.html { render_row_turbo(:close) } if row_html_turbo_allowed?
219
226
  format.js { render :close }
220
227
  end
221
228
  end
@@ -224,10 +231,9 @@ class InlineFormsController < ApplicationController
224
231
  def soft_restore
225
232
  @update_span = params[:update]
226
233
  @object = referenced_object
227
- # restore the object
228
234
  @object.soft_restore
229
235
  respond_to do |format|
230
- format.html { } unless @Klass.not_accessible_through_html?
236
+ format.html { render_row_turbo(:close) } if row_html_turbo_allowed?
231
237
  format.js { render :close }
232
238
  end
233
239
  end
@@ -237,11 +243,10 @@ class InlineFormsController < ApplicationController
237
243
  @update_span = params[:update]
238
244
  @object = referenced_object
239
245
  if current_user.role? :superadmin
240
- # destroy the object
241
- @undo_object = @object.versions.last
246
+ @undo_version = @object.versions.last
242
247
  @object.destroy
243
248
  respond_to do |format|
244
- format.html { } unless @Klass.not_accessible_through_html?
249
+ format.html { render_row_turbo_destroyed } if row_html_turbo_allowed?
245
250
  format.js { render :record_destroyed }
246
251
  end
247
252
  end
@@ -251,14 +256,13 @@ class InlineFormsController < ApplicationController
251
256
  # Thanks Ryan Bates: http://railscasts.com/episodes/255-undo-with-paper-trail
252
257
  def revert
253
258
  @update_span = params[:update]
254
- @object = referenced_object
255
259
  if current_user.role? :superadmin
256
260
  @version = PaperTrail::Version.find(params[:id])
257
- @version.reify.save!
258
- @object = @Klass.find(@version.item_id)
261
+ @object = @version.reify
262
+ @object.save!
259
263
  authorize!(:revert, @object) if cancan_enabled?
260
264
  respond_to do |format|
261
- format.html { } unless @Klass.not_accessible_through_html?
265
+ format.html { render_row_turbo(:close) } if row_html_turbo_allowed?
262
266
  format.js { render :close }
263
267
  end
264
268
  end
@@ -277,11 +281,103 @@ class InlineFormsController < ApplicationController
277
281
  end
278
282
 
279
283
  private
284
+
285
+ # HTML field edit/show inside a +<turbo-frame>+ (Step 3). Scalar fields no longer
286
+ # use UJS; +format.html+ is always registered for edit/update/single-attribute show.
287
+ #
288
+ # +@inline_forms_turbo_field+ tells +link_to_inline_edit+ (and the per-+form_element+
289
+ # +*_show+ helpers it wraps) to emit Turbo data attributes. The flag is set in
290
+ # +_show.html.erb+ when a row first opens, but bare +field_show+ / +field_edit+
291
+ # responses (on +cancel+ / +update+) do not re-render +_show+. Without setting
292
+ # it here the link in the swapped frame falls back to +remote: true+, which the
293
+ # legacy +jquery_ujs+ bundle intercepts as a JS request -- the controller only
294
+ # registers +format.html+, so the click silently fails (no swap, no edit form).
295
+ def render_turbo_field(template, turbo_field_show: false)
296
+ @turbo_frame = true if template == :field_edit
297
+ @turbo_field_show_turbo_frame = turbo_field_show
298
+ @inline_forms_turbo_field = true
299
+ render "inline_forms/#{template}", layout: "turbo_rails/frame"
300
+ end
301
+
302
+ # Top-level list row open/close (Step 3): full `_show` / `_close` inside
303
+ # `<turbo-frame id="…">` matching `params[:update]`.
304
+ def render_row_turbo(mode)
305
+ @inline_forms_turbo_row = true
306
+ template = (mode == :close) ? "inline_forms/row_close" : "inline_forms/row_show"
307
+ layout = turbo_frame_request? ? "turbo_rails/frame" : "inline_forms"
308
+ render template, layout: layout
309
+ end
310
+
311
+ def render_row_turbo_destroyed
312
+ @inline_forms_turbo_row = true
313
+ layout = turbo_frame_request? ? "turbo_rails/frame" : "inline_forms"
314
+ render "inline_forms/row_destroyed", layout: layout
315
+ end
316
+
317
+ # Nested has_many +new+ / cancel / +create+ inside a parent +<turbo-frame>+ (e.g. Apartment → Photo).
318
+ def associated_list_html_allowed?
319
+ @parent_class.present? && params[:update].present?
320
+ end
321
+
322
+ def associated_list_frame_layout
323
+ # Use full inline_forms chrome so the swapped frame is styled; Turbo extracts
324
+ # the matching <turbo-frame id="…"> from the response body.
325
+ "inline_forms"
326
+ end
327
+
328
+ def render_turbo_new
329
+ @turbo_frame = true
330
+ render "inline_forms/new_record", layout: associated_list_frame_layout
331
+ end
332
+
333
+ # Nested +index+ / cancel / +create+ HTML inside a parent-associated +<turbo-frame>+.
334
+ def render_nested_associated_list_html
335
+ if turbo_frame_request? && nested_list_frame_id?(params[:update])
336
+ # Pagination and other swaps targeting the inner +…_photos_list+ frame: minimal layout.
337
+ @ul_needed = true
338
+ render "inline_forms/_list", layout: "turbo_rails/frame"
339
+ elsif turbo_frame_request? && params[:update].present?
340
+ # Cancel / +create+ targeting the outer +apartment_<id>_photos+ frame: styled full layout.
341
+ render_associated_list_frame
342
+ else
343
+ frame_layout = turbo_frame_request? ? "turbo_rails/frame" : "inline_forms"
344
+ render "inline_forms/_list", layout: frame_layout
345
+ end
346
+ end
347
+
348
+ # +apartment_1_photos_list+ (inner list) vs +apartment_1_photos+ (outer associated container).
349
+ def nested_list_frame_id?(update)
350
+ update.to_s.end_with?("_list")
351
+ end
352
+
353
+ # After nested +create+ / cancel; restores list inside the outer associated frame.
354
+ def render_associated_list_frame
355
+ @ul_needed = true
356
+ render "inline_forms/create_list_frame", layout: associated_list_frame_layout
357
+ end
358
+
359
+ # HTML row open/close is allowed for normal models, and for +not_accessible_through_html?+
360
+ # models (e.g. Photo) when the request targets a nested associated list row
361
+ # (+params[:update]+ like +apartment_1_photo_5+), not bare top-level CRUD.
362
+ def row_html_turbo_allowed?
363
+ return true unless @Klass.not_accessible_through_html?
364
+ nested_associated_list_row_update?(params[:update])
365
+ end
366
+
367
+ # +apartment_1_photo_5+ → +["apartment","1","photo","5"]+ (≥4 segments, trailing id).
368
+ # Differs from field spans (+apartment_1_photo_5_name+ ends with letters) and
369
+ # top-level rows (+apartment_1+ — too few segments).
370
+ def nested_associated_list_row_update?(update)
371
+ parts = update.to_s.split("_")
372
+ parts.length >= 4 && parts.last.match?(/\A\d+\z/)
373
+ end
374
+
280
375
  # Get the class from the controller name.
281
376
  # CountryController < InlineFormsController, so what class are we?
282
377
  # TODO think about this a bit more.
283
378
  def getKlass #:doc:
284
379
  @Klass = self.controller_name.classify.constantize
380
+ InlineForms.validate_plain_text_configuration_for!(@Klass)
285
381
  @Klass
286
382
  end
287
383
 
@@ -1,41 +1,15 @@
1
1
  # -*- encoding : utf-8 -*-
2
+ # Legacy type name: CKEditor is removed; behaves like :rich_text.
2
3
  InlineForms::SPECIAL_COLUMN_TYPES[:ckeditor]=:text
3
4
 
4
5
  def ckeditor_show(object, attribute)
5
- link_to_inline_edit object,
6
- attribute,
7
- '<div class="ckeditor_area">'.html_safe +
8
- cktext_area_tag(
9
- attribute,
10
- object[attribute],
11
- :id => "textarea_#{object.class.name.underscore}_#{object.id}_#{attribute.to_s}",
12
- :ckeditor => { :width => '100%',
13
- :height => '200px',
14
- :toolbar => "None",
15
- :readOnly => "true",
16
- :resize_enabled => "false",
17
- :toolbarCanCollapse => "false"
18
- }
19
- ) +
20
- image_tag( 'inline_forms/glass_plate.gif',
21
- :class => "glass_plate",
22
- :title => '' ) +
23
- "<script>delete CKEDITOR.instances['textarea_#{object.class.name.underscore}_#{object.id}_#{attribute.to_s}']</script>".html_safe +
24
- '</div>'.html_safe,
25
- from_callee: __callee__
6
+ rich_text_show(object, attribute)
26
7
  end
27
8
 
28
9
  def ckeditor_edit(object, attribute)
29
- cktext_area_tag( attribute,
30
- object[attribute],
31
- :id => "textarea_#{object.class.name.underscore}_#{object.id}_#{attribute.to_s}",
32
- :ckeditor => { :width => '100%',
33
- :height => '200px'
34
- }
35
- ) +
36
- "<script>delete CKEDITOR.instances['textarea_#{object.class.name.underscore}_#{object.id}_#{attribute.to_s}']</script>".html_safe
10
+ rich_text_edit(object, attribute)
37
11
  end
38
12
 
39
13
  def ckeditor_update(object, attribute)
40
- object[attribute.to_sym] = params[attribute.to_sym]
14
+ rich_text_update(object, attribute)
41
15
  end