hibiki_rails 0.5.1 → 0.6.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +97 -0
- data/lib/generators/hibiki/rails/css_variant.rb +11 -2
- data/lib/generators/hibiki/rails/scaffold/USAGE +0 -1
- data/lib/generators/hibiki/rails/scaffold_controller/USAGE +3 -5
- data/lib/generators/hibiki/rails/scaffold_controller/scaffold_controller_generator.rb +18 -20
- data/lib/generators/hibiki/rails/scaffold_controller/templates/daisyui/views/_pagination.html.erb.tt +18 -12
- data/lib/generators/hibiki/rails/scaffold_controller/templates/none/views/_pagination.html.erb.tt +18 -12
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/daisyui/views/pagination.rb.tt +14 -9
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/none/views/pagination.rb.tt +14 -9
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/field_error.rb.tt +8 -3
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/form.rb.tt +2 -14
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/form_errors.rb.tt +29 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/list.rb.tt +17 -3
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/row.rb.tt +2 -2
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/row_form.rb.tt +1 -1
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/tailwind/views/pagination.rb.tt +14 -9
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/channel.rb.tt +3 -1
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/member_channel.rb.tt +7 -5
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/query.rb.tt +7 -5
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_field_error.html.erb.tt +6 -1
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_form.html.erb.tt +1 -11
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_form_errors.html.erb.tt +24 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_list.html.erb.tt +11 -3
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_row.html.erb.tt +5 -4
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_row_form.html.erb.tt +1 -1
- data/lib/generators/hibiki/rails/scaffold_controller/templates/tailwind/views/_pagination.html.erb.tt +18 -12
- data/lib/generators/hibiki/rails/scaffold_helpers.rb +13 -6
- data/lib/generators/hibiki/rails/scaffold_model_injection.rb +6 -31
- data/lib/generators/hibiki/rails/scaffold_phlex_helpers.rb +5 -3
- data/lib/generators/hibiki/rails/scaffold_post_install.rb +4 -2
- data/lib/generators/hibiki/rails/scaffold_schema.rb +4 -11
- data/lib/generators/hibiki/rails/scaffold_shared_views.rb +76 -0
- data/lib/generators/hibiki/rails/scaffold_transport_stylesheet.rb +20 -8
- data/lib/hibiki/rails/engine.rb +8 -0
- data/lib/hibiki/rails/swallowed_write_warning.rb +43 -0
- data/lib/hibiki/rails/version.rb +1 -1
- data/lib/hibiki/rails.rb +30 -0
- metadata +7 -4
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/row.rb.tt +0 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d708875aa67bf5d9b9137ac8d9bb05cf23604db936011706d068ec0b7116429c
|
|
4
|
+
data.tar.gz: 941b16f996127706d699e8812cf712b755c0f833f7a04c9e71912844421645ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 74175d980c70f1b4ea1bb9ba593ef9940326eccfce6c783ceefdebfb2a0659706faa1040393759549366c5880ea23459740e0f3f280e64fb47a4bc32a371f69c
|
|
7
|
+
data.tar.gz: c88a903f15b13516310beb32a7ed741942c49ae65823a6422fb36823400a9fea420ae08bd2fe047bc1d2e6ff3e38cef8f86c0665eeee6014e0f495c880ad90a4
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,103 @@ The gem and the npm package are released in lockstep and share these version
|
|
|
4
4
|
numbers — `app/assets/javascripts/hibiki.js` is a single copy served both ways,
|
|
5
5
|
so importmap and bundler apps always resolve identical client code.
|
|
6
6
|
|
|
7
|
+
## 0.6.0 — 2026-08-10
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
**`Hibiki::Rails.record_equals` — a per-signal comparator for ActiveRecord
|
|
12
|
+
records.** `ActiveRecord#==` compares class and id only, so a stale record is
|
|
13
|
+
`==` to its edited reload and a signal write carrying the fresh one is silently
|
|
14
|
+
dropped. Pass the comparator per signal and the write goes through:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
state(:items, equals: Hibiki::Rails.record_equals) { fetch }
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
It compares class + `attributes`, recurses through arrays, and falls back to
|
|
21
|
+
`==` for everything else. It needs hibiki 0.3.0's `equals:` (see the dependency
|
|
22
|
+
change below), which is honored at both of the graph's equality gates — the
|
|
23
|
+
write gate and the flush-time check — so a change that passes the comparator
|
|
24
|
+
actually reaches the page. This is opt-in sugar that makes the snapshot pattern
|
|
25
|
+
forgiving; "records stop at the boundary" stays the documented default, and a
|
|
26
|
+
comparator still cannot see an in-place mutation that never enters the write
|
|
27
|
+
path.
|
|
28
|
+
|
|
29
|
+
**A development-mode warning for the write that pattern swallows.** When a
|
|
30
|
+
`State` write is dropped by the default `==` but the old and new values'
|
|
31
|
+
`attributes` differ — the classic silent-stale-UI debugging session — the log
|
|
32
|
+
now says so and points at the docs. Development only (never test or
|
|
33
|
+
production), zero semantic change: the write is still dropped.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
**The scaffold's row projection is gone — records now cross the boundary as
|
|
38
|
+
frozen snapshots.** `app/models/book_row.rb` is no longer generated. The query
|
|
39
|
+
object's `rows` returns real records, hardened at the boundary —
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
def rows
|
|
43
|
+
@rows ||= window_scope.strict_loading.map { it.readonly!; it.freeze }
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
— and the channels' `rows`/`row` deriveds compare them with
|
|
48
|
+
`equals: Hibiki::Rails.record_equals`. What the `Data` projection's structural
|
|
49
|
+
`==` used to provide, the comparator provides; what it could never provide, the
|
|
50
|
+
freeze triple does: an attribute write raises `FrozenError`, `save` raises
|
|
51
|
+
`ActiveRecord::ReadOnlyRecord`, and an unpreloaded association walk raises
|
|
52
|
+
`ActiveRecord::StrictLoadingViolationError` instead of firing a lazy query off
|
|
53
|
+
the graph thread. Views print a `belongs_to` label as `book.author&.name`
|
|
54
|
+
directly, so **the scaffold no longer injects a `delegate` per `belongs_to`
|
|
55
|
+
into the model** — the model injection is the `after_commit` ping alone. The
|
|
56
|
+
member channel's fetch preloads its associations (`includes(...) +
|
|
57
|
+
strict_loading`) for the same reason.
|
|
58
|
+
|
|
59
|
+
Existing scaffolded apps keep working untouched: their `book_row.rb` and
|
|
60
|
+
delegates are app code, and the runtime reads none of it. Re-running a scaffold
|
|
61
|
+
with `--force` moves the resource over; the generator never deletes, so the
|
|
62
|
+
orphaned `*_row.rb` stays on disk for you to remove.
|
|
63
|
+
|
|
64
|
+
**The hibiki dependency floor is `~> 0.3`** (per-signal `equals:`, shipped in
|
|
65
|
+
hibiki 0.3.0). Everything 0.2 provided still holds; generated channels now rely
|
|
66
|
+
on the comparator being consulted at both equality gates.
|
|
67
|
+
|
|
68
|
+
**The page control and the field-error line are shared partials now.** Each
|
|
69
|
+
scaffold used to write its own copy per resource; both are presentation-only,
|
|
70
|
+
so they are emitted once per app instead — `app/views/shared/_pagination.html.erb`
|
|
71
|
+
and `_field_error.html.erb` (under `--phlex`: `Views::Shared::Pagination` and
|
|
72
|
+
`Views::Shared::FieldError` in `app/views/shared/`). Everything resource-shaped
|
|
73
|
+
reaches the page control as locals from the list partial. A second scaffold
|
|
74
|
+
finds the files present and leaves them; if they were generated under a
|
|
75
|
+
different `--css` style, a notice says so instead of silently restyling every
|
|
76
|
+
other resource's control.
|
|
77
|
+
|
|
78
|
+
**Pagination renders above the list as well as below**, so a long page starts
|
|
79
|
+
with a control in reach. Both copies live inside the re-rendered fragment and
|
|
80
|
+
carry distinct ids (`books_pagination_top` / `books_pagination`), so idiomorph
|
|
81
|
+
updates them in place.
|
|
82
|
+
|
|
83
|
+
Re-running a scaffold with `--force` moves the list over to the shared
|
|
84
|
+
partials and prints a notice naming the now-dead per-resource copies — the
|
|
85
|
+
generator never deletes them itself.
|
|
86
|
+
|
|
87
|
+
**The form's error summary is a shared partial too** —
|
|
88
|
+
`app/views/shared/_form_errors.html.erb` (under `--phlex`:
|
|
89
|
+
`Views::Shared::FormErrors`), rendered by every scaffolded full-page form. The
|
|
90
|
+
resource name comes off the record at render time, so one file serves every
|
|
91
|
+
scaffold. Under `--css=tailwind` and `--css=daisyui` the block is styled as a
|
|
92
|
+
red alert panel instead of the scaffold-stock `style="color: red"`, which
|
|
93
|
+
`--css=none` keeps.
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
**The `hibiki_busy.css` import lands directly below `@import "tailwindcss";`**
|
|
98
|
+
instead of at the end of the entry stylesheet — appending placed it after
|
|
99
|
+
`@plugin` lines, and CSS requires every `@import` before any other statement.
|
|
100
|
+
Also fixed: re-running a scaffold in a tailwindcss-rails app
|
|
101
|
+
(`app/assets/tailwind/application.css`) duplicated the import on every run,
|
|
102
|
+
because the idempotence probe checked the wrong path spelling.
|
|
103
|
+
|
|
7
104
|
## 0.5.1 — 2026-08-08
|
|
8
105
|
|
|
9
106
|
### Changed
|
|
@@ -87,10 +87,19 @@ module Hibiki
|
|
|
87
87
|
spinner: "loading loading-spinner loading-xs",
|
|
88
88
|
warning_text: "text-warning",
|
|
89
89
|
error_text: "text-error text-sm mt-1",
|
|
90
|
+
# The full-form error summary. Plain Tailwind on purpose, so both
|
|
91
|
+
# styled variants share it via the merge — an addition (2026-08-09),
|
|
92
|
+
# not a transcription: the reference apps carried the scaffold-stock
|
|
93
|
+
# `style="color: red"` block, which --css=none still emits.
|
|
94
|
+
error_summary: "rounded-md border border-red-200 bg-red-50 p-4 mb-6",
|
|
95
|
+
error_summary_title: "text-sm font-semibold text-red-800",
|
|
96
|
+
error_summary_list: "mt-2 list-disc list-inside text-sm text-red-700",
|
|
90
97
|
muted: "opacity-60 italic",
|
|
91
98
|
muted_inline: "opacity-60",
|
|
92
99
|
counts: "ml-auto text-sm opacity-70",
|
|
93
|
-
|
|
100
|
+
# my-, not mt-: the control renders above the list as well as below,
|
|
101
|
+
# and the top copy needs the gap on its other side.
|
|
102
|
+
pagination_nav: "join my-2 mx-auto flex justify-center w-fit"
|
|
94
103
|
}.freeze
|
|
95
104
|
|
|
96
105
|
# DaisyUI is a plugin over Tailwind, and the merge says so literally:
|
|
@@ -135,7 +144,7 @@ module Hibiki
|
|
|
135
144
|
muted: "text-gray-500 italic",
|
|
136
145
|
muted_inline: "text-gray-400",
|
|
137
146
|
counts: "ml-auto text-sm text-gray-500",
|
|
138
|
-
pagination_nav: "
|
|
147
|
+
pagination_nav: "my-2 mx-auto flex justify-center w-fit -space-x-px rounded-md shadow-sm"
|
|
139
148
|
).freeze
|
|
140
149
|
|
|
141
150
|
# Every lookup misses, so every class argument is omitted entirely.
|
|
@@ -9,17 +9,15 @@ Description:
|
|
|
9
9
|
reactive, edits happen in place, and changes from anywhere update every open
|
|
10
10
|
list.
|
|
11
11
|
|
|
12
|
-
Two models are MODIFIED. This one gets
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
are left alone.
|
|
12
|
+
Two models are MODIFIED. This one gets an after_commit broadcast. The
|
|
13
|
+
related models get the missing has_many association and a ping so changes
|
|
14
|
+
are reflected here. Existing declarations are left alone.
|
|
16
15
|
|
|
17
16
|
Example:
|
|
18
17
|
bin/rails generate hibiki:rails:scaffold_controller Book
|
|
19
18
|
|
|
20
19
|
This will create:
|
|
21
20
|
app/models/book_query.rb
|
|
22
|
-
app/models/book_row.rb
|
|
23
21
|
app/channels/books_channel.rb
|
|
24
22
|
app/channels/book_channel.rb
|
|
25
23
|
app/forms/book_form.rb
|
|
@@ -10,6 +10,7 @@ require_relative "../scaffold_parent_injection"
|
|
|
10
10
|
require_relative "../scaffold_parent_notices"
|
|
11
11
|
require_relative "../scaffold_phlex_helpers"
|
|
12
12
|
require_relative "../scaffold_post_install"
|
|
13
|
+
require_relative "../scaffold_shared_views"
|
|
13
14
|
require_relative "../scaffold_transport_stylesheet"
|
|
14
15
|
require_relative "../scaffold_schema"
|
|
15
16
|
require_relative "../css_variant"
|
|
@@ -36,6 +37,7 @@ module Hibiki
|
|
|
36
37
|
include ScaffoldParentInjection
|
|
37
38
|
include ScaffoldParentNotices
|
|
38
39
|
include ScaffoldPostInstall
|
|
40
|
+
include ScaffoldSharedViews
|
|
39
41
|
include ScaffoldTransportStylesheet
|
|
40
42
|
|
|
41
43
|
TEMPLATE_ROOT = File.expand_path("templates", __dir__)
|
|
@@ -100,7 +102,6 @@ module Hibiki
|
|
|
100
102
|
|
|
101
103
|
def create_models
|
|
102
104
|
template "query.rb.tt", query_path
|
|
103
|
-
template "row.rb.tt", row_path
|
|
104
105
|
end
|
|
105
106
|
|
|
106
107
|
def create_form
|
|
@@ -112,27 +113,26 @@ module Hibiki
|
|
|
112
113
|
end
|
|
113
114
|
|
|
114
115
|
def create_views
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
if phlex?
|
|
117
|
+
create_phlex_views
|
|
118
|
+
else
|
|
119
|
+
%w[index show new edit _form _list _controls].each do |view|
|
|
120
|
+
template "views/#{view}.html.erb.tt", view_path("#{view}.html.erb")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
template "views/_row.html.erb.tt", view_path("_#{row_partial}.html.erb")
|
|
124
|
+
template "views/_row_form.html.erb.tt", view_path("_#{row_form_partial}.html.erb")
|
|
119
125
|
end
|
|
120
126
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
# Under --infinite-scroll the sentinel is inlined in _list and there
|
|
125
|
-
# is no page control at all — not an empty one.
|
|
126
|
-
template "views/_pagination.html.erb.tt", view_path("_pagination.html.erb") unless infinite?
|
|
127
|
+
# The page control and the field-error line, once per app — see
|
|
128
|
+
# ScaffoldSharedViews.
|
|
129
|
+
create_shared_views
|
|
127
130
|
end
|
|
128
131
|
|
|
129
132
|
# The model being scaffolded — one of two files this generator modifies
|
|
130
|
-
# that the app already owned.
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
# prints an association's LABEL, and the show page hands that partial a
|
|
134
|
-
# live record — so without the delegate the show page raises on arrival.
|
|
135
|
-
# Once we are editing the model anyway, the ping belongs here too.
|
|
133
|
+
# that the app already owned. It gets the after_commit ping, which is
|
|
134
|
+
# what makes another tab, another user, the plain controller and a
|
|
135
|
+
# console write all reach an open list.
|
|
136
136
|
#
|
|
137
137
|
# The alternative — printing it and hoping — fails SILENTLY: everything
|
|
138
138
|
# works in one tab, and cross-tab plus out-of-band writes simply never
|
|
@@ -181,11 +181,9 @@ module Hibiki
|
|
|
181
181
|
#
|
|
182
182
|
# Private, because a public method on a Thor generator is a command.
|
|
183
183
|
def create_phlex_views
|
|
184
|
-
%w[index show new edit form list row row_form controls
|
|
184
|
+
%w[index show new edit form list row row_form controls].each do |view|
|
|
185
185
|
template "views/#{view}.rb.tt", view_path("#{view}.rb")
|
|
186
186
|
end
|
|
187
|
-
|
|
188
|
-
template "views/pagination.rb.tt", view_path("pagination.rb") unless infinite?
|
|
189
187
|
end
|
|
190
188
|
|
|
191
189
|
# Ordered so an app's own lib/templates/... override wins first, then
|
data/lib/generators/hibiki/rails/scaffold_controller/templates/daisyui/views/_pagination.html.erb.tt
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
<%#- The ONE component with a per-variant template. DaisyUI expresses a page
|
|
2
2
|
item as two tokens (`join-item btn`), so each of the six variants below can
|
|
3
|
-
repeat them inline — which is exactly what plain Tailwind cannot do.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
repeat them inline — which is exactly what plain Tailwind cannot do.
|
|
4
|
+
|
|
5
|
+
Emitted ONCE per app into app/views/shared/, so nothing here may name the
|
|
6
|
+
resource: everything resource-shaped (the wrapper id, the anchor target,
|
|
7
|
+
the page-number window) arrives as a local from the list partial. The
|
|
8
|
+
`--css=` marker below is how a later scaffold run detects a variant
|
|
9
|
+
mismatch — keep it. -%>
|
|
10
|
+
<%%# locals: (page: 1, page_count: 1, pages: [], anchor:, id:) -%>
|
|
11
|
+
<%%# Shared page control (--css=<%= css_variant %>), rendered above and below
|
|
12
|
+
every scaffolded list. The links are anchors to the list, so a click also
|
|
13
|
+
does the browser's own fragment jump; data-turbo="false" keeps Turbo Drive
|
|
14
|
+
out. `on` returns a { data: } hash — merge, don't splat, or the anchor
|
|
15
|
+
stops scrolling. %>
|
|
10
16
|
<%% page_data = ->(n) { { turbo: false }.merge(on(:go_to_page, with: { page: n })[:data]) } %>
|
|
11
17
|
<%%# The wrapper always renders (so idiomorph has a node to pair with) and
|
|
12
18
|
stays bare — layout classes here would leave dead space when empty. %>
|
|
13
|
-
<div id="
|
|
19
|
+
<div id="<%%= id %>">
|
|
14
20
|
<%% if page_count > 1 %>
|
|
15
21
|
<nav<%= css_attr(:pagination_nav) %> aria-label="Pagination">
|
|
16
22
|
<%%# Prev/next go inert at the ends rather than disappearing, so the
|
|
@@ -18,17 +24,17 @@
|
|
|
18
24
|
<%% if page == 1 %>
|
|
19
25
|
<span class="join-item btn btn-disabled" aria-disabled="true">«</span>
|
|
20
26
|
<%% else %>
|
|
21
|
-
<%%= link_to "«",
|
|
27
|
+
<%%= link_to "«", anchor, class: "join-item btn", rel: "prev",
|
|
22
28
|
"aria-label": "Previous page", data: page_data.(page - 1) %>
|
|
23
29
|
<%% end %>
|
|
24
30
|
|
|
25
|
-
<%%
|
|
31
|
+
<%% pages.each do |n| %>
|
|
26
32
|
<%% if n.nil? %>
|
|
27
33
|
<span class="join-item btn btn-disabled" aria-hidden="true">…</span>
|
|
28
34
|
<%% elsif n == page %>
|
|
29
35
|
<span class="join-item btn btn-active" aria-current="page"><%%= n %></span>
|
|
30
36
|
<%% else %>
|
|
31
|
-
<%%= link_to n,
|
|
37
|
+
<%%= link_to n, anchor, class: "join-item btn",
|
|
32
38
|
"aria-label": "Page #{n}", data: page_data.(n) %>
|
|
33
39
|
<%% end %>
|
|
34
40
|
<%% end %>
|
|
@@ -36,7 +42,7 @@
|
|
|
36
42
|
<%% if page == page_count %>
|
|
37
43
|
<span class="join-item btn btn-disabled" aria-disabled="true">»</span>
|
|
38
44
|
<%% else %>
|
|
39
|
-
<%%= link_to "»",
|
|
45
|
+
<%%= link_to "»", anchor, class: "join-item btn", rel: "next",
|
|
40
46
|
"aria-label": "Next page", data: page_data.(page + 1) %>
|
|
41
47
|
<%% end %>
|
|
42
48
|
</nav>
|
data/lib/generators/hibiki/rails/scaffold_controller/templates/none/views/_pagination.html.erb.tt
CHANGED
|
@@ -3,16 +3,22 @@
|
|
|
3
3
|
no stylesheet there is nothing to carry that signal, so the current page
|
|
4
4
|
has to change ELEMENT — <strong> rather than a styled <span> — and the
|
|
5
5
|
inert prev/next markers become plain text. A class-token map cannot
|
|
6
|
-
express a change of tag name.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
express a change of tag name.
|
|
7
|
+
|
|
8
|
+
Emitted ONCE per app into app/views/shared/, so nothing here may name the
|
|
9
|
+
resource: everything resource-shaped (the wrapper id, the anchor target,
|
|
10
|
+
the page-number window) arrives as a local from the list partial. The
|
|
11
|
+
`--css=` marker below is how a later scaffold run detects a variant
|
|
12
|
+
mismatch — keep it. -%>
|
|
13
|
+
<%%# locals: (page: 1, page_count: 1, pages: [], anchor:, id:) -%>
|
|
14
|
+
<%%# Shared page control (--css=<%= css_variant %>), rendered above and below
|
|
15
|
+
every scaffolded list. The links are anchors to the list, so a click also
|
|
16
|
+
does the browser's own fragment jump; data-turbo="false" keeps Turbo Drive
|
|
17
|
+
out. `on` returns a { data: } hash — merge, don't splat, or the anchor
|
|
18
|
+
stops scrolling. %>
|
|
13
19
|
<%% page_data = ->(n) { { turbo: false }.merge(on(:go_to_page, with: { page: n })[:data]) } %>
|
|
14
20
|
<%%# The wrapper always renders, so idiomorph always has a node to pair with. %>
|
|
15
|
-
<div id="
|
|
21
|
+
<div id="<%%= id %>">
|
|
16
22
|
<%% if page_count > 1 %>
|
|
17
23
|
<nav aria-label="Pagination">
|
|
18
24
|
<%%# Prev/next go inert at the ends rather than disappearing, so the
|
|
@@ -20,24 +26,24 @@
|
|
|
20
26
|
<%% if page == 1 %>
|
|
21
27
|
<span aria-disabled="true">«</span>
|
|
22
28
|
<%% else %>
|
|
23
|
-
<%%= link_to "«",
|
|
29
|
+
<%%= link_to "«", anchor, rel: "prev",
|
|
24
30
|
"aria-label": "Previous page", data: page_data.(page - 1) %>
|
|
25
31
|
<%% end %>
|
|
26
32
|
|
|
27
|
-
<%%
|
|
33
|
+
<%% pages.each do |n| %>
|
|
28
34
|
<%% if n.nil? %>
|
|
29
35
|
<span aria-hidden="true">…</span>
|
|
30
36
|
<%% elsif n == page %>
|
|
31
37
|
<strong aria-current="page"><%%= n %></strong>
|
|
32
38
|
<%% else %>
|
|
33
|
-
<%%= link_to n,
|
|
39
|
+
<%%= link_to n, anchor, "aria-label": "Page #{n}", data: page_data.(n) %>
|
|
34
40
|
<%% end %>
|
|
35
41
|
<%% end %>
|
|
36
42
|
|
|
37
43
|
<%% if page == page_count %>
|
|
38
44
|
<span aria-disabled="true">»</span>
|
|
39
45
|
<%% else %>
|
|
40
|
-
<%%= link_to "»",
|
|
46
|
+
<%%= link_to "»", anchor, rel: "next",
|
|
41
47
|
"aria-label": "Next page", data: page_data.(page + 1) %>
|
|
42
48
|
<%% end %>
|
|
43
49
|
</nav>
|
data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/daisyui/views/pagination.rb.tt
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
<%#- ESCAPING: none — plain Ruby out, every <% is generation time.
|
|
1
|
+
<%#- ESCAPING: none — plain Ruby out, every <% is generation time. Emitted ONCE
|
|
2
|
+
per app into app/views/shared/, so nothing here may name the resource:
|
|
3
|
+
everything resource-shaped arrives as a keyword from the list component.
|
|
4
|
+
The `--css=` marker in the class comment is how a later scaffold run
|
|
5
|
+
detects a variant mismatch — keep it. -%>
|
|
2
6
|
# frozen_string_literal: true
|
|
3
7
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# browser's own fragment jump; data-turbo="false" keeps Turbo Drive
|
|
7
|
-
|
|
8
|
+
# Shared page control (--css=<%= css_variant %>), rendered above and below
|
|
9
|
+
# every scaffolded list. The links are anchors to the list, so a click also
|
|
10
|
+
# does the browser's own fragment jump; data-turbo="false" keeps Turbo Drive
|
|
11
|
+
# out.
|
|
12
|
+
class Views::Shared::Pagination < Views::Base
|
|
8
13
|
<%= phlex_includes(:link_to, hibiki: true) %>
|
|
9
14
|
|
|
10
|
-
<%= initializer_source([%w[page 1], %w[page_count 1]]) %>
|
|
15
|
+
<%= initializer_source([%w[page 1], %w[page_count 1], %w[pages []], ["anchor"], ["id"]]) %>
|
|
11
16
|
|
|
12
17
|
def view_template
|
|
13
18
|
# The wrapper always renders (so idiomorph has a node to pair with) and
|
|
14
19
|
# stays bare — layout classes here would leave dead space when empty.
|
|
15
|
-
div(id:
|
|
20
|
+
div(id: @id) do
|
|
16
21
|
nav<%= arg_list(*css_args(:pagination_nav), %(aria_label: "Pagination")) %> { items } if @page_count > 1
|
|
17
22
|
end
|
|
18
23
|
end
|
|
@@ -25,7 +30,7 @@ class <%= view_class_name(:pagination) %> < Views::Base
|
|
|
25
30
|
@page == 1 ? inert("«") : step("«", @page - 1, rel: "prev", label: "Previous page")
|
|
26
31
|
whitespace
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
@pages.each do |n|
|
|
29
34
|
if n.nil?
|
|
30
35
|
span(class: "join-item btn btn-disabled", aria_hidden: "true") { "…" }
|
|
31
36
|
elsif n == @page
|
|
@@ -48,7 +53,7 @@ class <%= view_class_name(:pagination) %> < Views::Base
|
|
|
48
53
|
# The STRING "false", not the boolean — Phlex omits false-valued attributes.
|
|
49
54
|
# And merge, don't splat: the anchor needs its own data attribute too.
|
|
50
55
|
def step(text, n, label:, rel: nil)
|
|
51
|
-
link_to text,
|
|
56
|
+
link_to text, @anchor, class: "join-item btn", rel:, "aria-label": label,
|
|
52
57
|
data: { turbo: "false" }.merge(on(:go_to_page, with: { page: n })[:data])
|
|
53
58
|
end
|
|
54
59
|
end
|
data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/none/views/pagination.rb.tt
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
<%#- ESCAPING: none — plain Ruby out, every <% is generation time.
|
|
1
|
+
<%#- ESCAPING: none — plain Ruby out, every <% is generation time. Emitted ONCE
|
|
2
|
+
per app into app/views/shared/, so nothing here may name the resource:
|
|
3
|
+
everything resource-shaped arrives as a keyword from the list component.
|
|
4
|
+
The `--css=` marker in the class comment is how a later scaffold run
|
|
5
|
+
detects a variant mismatch — keep it. -%>
|
|
2
6
|
# frozen_string_literal: true
|
|
3
7
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# browser's own fragment jump; data-turbo="false" keeps Turbo Drive
|
|
8
|
+
# Shared page control (--css=<%= css_variant %>), rendered above and below
|
|
9
|
+
# every scaffolded list. The links are anchors to the list, so a click also
|
|
10
|
+
# does the browser's own fragment jump; data-turbo="false" keeps Turbo Drive
|
|
11
|
+
# out.
|
|
7
12
|
#
|
|
8
13
|
# Unstyled, so the current page changes ELEMENT (strong) — there is no class
|
|
9
14
|
# to carry the signal.
|
|
10
|
-
class
|
|
15
|
+
class Views::Shared::Pagination < Views::Base
|
|
11
16
|
<%= phlex_includes(:link_to, hibiki: true) %>
|
|
12
17
|
|
|
13
|
-
<%= initializer_source([%w[page 1], %w[page_count 1]]) %>
|
|
18
|
+
<%= initializer_source([%w[page 1], %w[page_count 1], %w[pages []], ["anchor"], ["id"]]) %>
|
|
14
19
|
|
|
15
20
|
def view_template
|
|
16
21
|
# The wrapper always renders, so idiomorph always has a node to pair with.
|
|
17
|
-
div(id:
|
|
22
|
+
div(id: @id) do
|
|
18
23
|
nav(aria_label: "Pagination") { items } if @page_count > 1
|
|
19
24
|
end
|
|
20
25
|
end
|
|
@@ -27,7 +32,7 @@ class <%= view_class_name(:pagination) %> < Views::Base
|
|
|
27
32
|
@page == 1 ? inert("«") : step("«", @page - 1, rel: "prev", label: "Previous page")
|
|
28
33
|
whitespace
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
@pages.each do |n|
|
|
31
36
|
if n.nil?
|
|
32
37
|
span(aria_hidden: "true") { "…" }
|
|
33
38
|
elsif n == @page
|
|
@@ -50,7 +55,7 @@ class <%= view_class_name(:pagination) %> < Views::Base
|
|
|
50
55
|
# The STRING "false", not the boolean — Phlex omits false-valued attributes.
|
|
51
56
|
# And merge, don't splat: the anchor needs its own data attribute too.
|
|
52
57
|
def step(text, n, label:, rel: nil)
|
|
53
|
-
link_to text,
|
|
58
|
+
link_to text, @anchor, rel:, "aria-label": label,
|
|
54
59
|
data: { turbo: "false" }.merge(on(:go_to_page, with: { page: n })[:data])
|
|
55
60
|
end
|
|
56
61
|
end
|
data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/field_error.rb.tt
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
<%#- ESCAPING: a Phlex component is plain Ruby, so there is no runtime ERB here
|
|
2
2
|
and nothing to double-escape. Every <% in this tree is generation time,
|
|
3
|
-
which is the one genuine simplification the port buys.
|
|
3
|
+
which is the one genuine simplification the port buys.
|
|
4
|
+
|
|
5
|
+
Emitted ONCE per app into app/views/shared/. The `--css=` marker in the
|
|
6
|
+
class comment is how a later scaffold run detects a variant mismatch —
|
|
7
|
+
keep it. -%>
|
|
4
8
|
# frozen_string_literal: true
|
|
5
9
|
|
|
6
|
-
#
|
|
7
|
-
|
|
10
|
+
# Shared field-error line (--css=<%= css_variant %>): one field's live
|
|
11
|
+
# validation message, or nothing at all.
|
|
12
|
+
class Views::Shared::FieldError < Views::Base
|
|
8
13
|
<%= initializer_source([%w[message nil]]) %>
|
|
9
14
|
|
|
10
15
|
def view_template
|
data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/form.rb.tt
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
# Each belongs_to collection arrives as a keyword, never an ivar.
|
|
10
10
|
<% end -%>
|
|
11
11
|
class <%= view_class_name(:form) %> < Views::Base
|
|
12
|
-
<%= phlex_includes(:form_with
|
|
12
|
+
<%= phlex_includes(:form_with) %>
|
|
13
13
|
|
|
14
14
|
<%= initializer_source([[singular_name, nil], *schema.belongs_tos.map { [it.association_name.to_s.pluralize, "[]"] }]) %>
|
|
15
15
|
|
|
16
16
|
def view_template
|
|
17
17
|
form_with<%= arg_list("model: @#{singular_name}", *css_args(:form)) %> do |form|
|
|
18
|
-
|
|
18
|
+
<%= render_call(:form_errors, ["record: @#{singular_name}"], indent: 6, shared: true) %>
|
|
19
19
|
fields(form)
|
|
20
20
|
|
|
21
21
|
div { form.submit<%= css? ? %( class: "#{css(:btn_primary)}") : "" %> }
|
|
@@ -24,18 +24,6 @@ class <%= view_class_name(:form) %> < Views::Base
|
|
|
24
24
|
|
|
25
25
|
private
|
|
26
26
|
|
|
27
|
-
def errors
|
|
28
|
-
return if @<%= singular_name %>.errors.empty?
|
|
29
|
-
|
|
30
|
-
div(style: "color: red") do
|
|
31
|
-
h2 { "#{pluralize(@<%= singular_name %>.errors.count, 'error')} prohibited this <%= human_name.downcase %> from being saved:" }
|
|
32
|
-
|
|
33
|
-
ul do
|
|
34
|
-
@<%= singular_name %>.errors.each { |error| li { error.full_message } }
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
27
|
def fields(form)
|
|
40
28
|
div<%= arg_list(*css_args(:fieldset)) %> do
|
|
41
29
|
<% schema.columns.each_with_index do |column, index| -%>
|
data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/form_errors.rb.tt
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<%#- ESCAPING: a Phlex component is plain Ruby, so there is no runtime ERB here
|
|
2
|
+
and nothing to double-escape.
|
|
3
|
+
|
|
4
|
+
Emitted ONCE per app into app/views/shared/. The `--css=` marker in the
|
|
5
|
+
class comment is how a later scaffold run detects a variant mismatch —
|
|
6
|
+
keep it. Under --css=none the body is the scaffold-stock red block; the
|
|
7
|
+
styled variants dress the same structure. -%>
|
|
8
|
+
# frozen_string_literal: true
|
|
9
|
+
|
|
10
|
+
# Shared error summary (--css=<%= css_variant %>): every message that stopped
|
|
11
|
+
# the record from saving, or nothing at all. The resource name comes off the
|
|
12
|
+
# record, so one component serves every scaffold.
|
|
13
|
+
class Views::Shared::FormErrors < Views::Base
|
|
14
|
+
<%= phlex_includes(:pluralize) %>
|
|
15
|
+
|
|
16
|
+
<%= initializer_source([%w[record]]) %>
|
|
17
|
+
|
|
18
|
+
def view_template
|
|
19
|
+
return if @record.errors.empty?
|
|
20
|
+
|
|
21
|
+
div<%= css? ? arg_list(*css_args(:error_summary)) : %[(style: "color: red")] %> do
|
|
22
|
+
h2<%= arg_list(*css_args(:error_summary_title)) %> { "#{pluralize(@record.errors.count, 'error')} prohibited this #{@record.model_name.human.downcase} from being saved:" }
|
|
23
|
+
|
|
24
|
+
ul<%= arg_list(*css_args(:error_summary_list)) %> do
|
|
25
|
+
@record.errors.each { |error| li { error.full_message } }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/list.rb.tt
CHANGED
|
@@ -15,6 +15,13 @@ class <%= view_class_name(:list) %> < Views::Base
|
|
|
15
15
|
|
|
16
16
|
def view_template
|
|
17
17
|
div<%= arg_list(%(id: "#{list_dom_id}"), *css_args(:list)) %> do
|
|
18
|
+
<% unless infinite? -%>
|
|
19
|
+
# Both page-control copies live inside the replaced fragment on purpose,
|
|
20
|
+
# so they re-render with the list; their distinct ids keep idiomorph
|
|
21
|
+
# from rebuilding them.
|
|
22
|
+
pagination("<%= pagination_dom_id %>_top")
|
|
23
|
+
|
|
24
|
+
<% end -%>
|
|
18
25
|
@<%= controller_file_name %>.each do |<%= singular_name %>|
|
|
19
26
|
<%= render_call(:row, row_render_args(singular_name), indent: 8) %>
|
|
20
27
|
end
|
|
@@ -26,12 +33,19 @@ class <%= view_class_name(:list) %> < Views::Base
|
|
|
26
33
|
<% if infinite? -%>
|
|
27
34
|
load_more if @remaining.positive?
|
|
28
35
|
<% else -%>
|
|
29
|
-
|
|
30
|
-
# list. Stable child ids keep idiomorph from rebuilding it.
|
|
31
|
-
<%= render_call(:pagination, ["page: @page", "page_count: @page_count"], indent: 6) %>
|
|
36
|
+
pagination("<%= pagination_dom_id %>")
|
|
32
37
|
<% end -%>
|
|
33
38
|
end
|
|
34
39
|
end
|
|
40
|
+
<% unless infinite? -%>
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def pagination(id)
|
|
45
|
+
pages = <%= query_class_name %>.page_numbers(page: @page, page_count: @page_count)
|
|
46
|
+
<%= render_call(:pagination, ["page: @page", "page_count: @page_count", "pages: pages", %(anchor: "##{list_dom_id}"), "id: id"], indent: 4, shared: true) %>
|
|
47
|
+
end
|
|
48
|
+
<% end -%>
|
|
35
49
|
<% if infinite? -%>
|
|
36
50
|
|
|
37
51
|
private
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
# One row, in either of its two modes.
|
|
8
8
|
#
|
|
9
|
-
# `@<%= singular_name %>` is
|
|
10
|
-
# the
|
|
9
|
+
# `@<%= singular_name %>` is a record either way: a frozen read-only snapshot
|
|
10
|
+
# from the channel, live from the show page. Rendered by the channel through
|
|
11
11
|
# ApplicationController.render, which has no controller context — everything
|
|
12
12
|
# this component branches on is a keyword argument instead.
|
|
13
13
|
class <%= view_class_name(:row) %> < Views::Base
|
data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/row_form.rb.tt
CHANGED
|
@@ -57,7 +57,7 @@ class <%= view_class_name(:row_form) %> < Views::Base
|
|
|
57
57
|
<% else -%>
|
|
58
58
|
<%= column.tag_field_helper %> <%= wrapped_list(row_field_args(column), indent: column.tag_field_helper.length + 5).lstrip %>
|
|
59
59
|
<% end -%>
|
|
60
|
-
<%= render_call(:field_error, [%(message: #{form_ref}.error_for(:#{column.name}))], indent: 4) %>
|
|
60
|
+
<%= render_call(:field_error, [%(message: #{form_ref}.error_for(:#{column.name}))], indent: 4, shared: true) %>
|
|
61
61
|
<% end -%>
|
|
62
62
|
end
|
|
63
63
|
|