plutonium 0.33.1 → 0.34.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/# Plutonium: The pre-alpha demo.md +4 -2
- data/.claude/skills/assets/SKILL.md +416 -0
- data/.claude/skills/connect-resource/SKILL.md +112 -0
- data/.claude/skills/controller/SKILL.md +302 -0
- data/.claude/skills/create-resource/SKILL.md +240 -0
- data/.claude/skills/definition/SKILL.md +218 -0
- data/.claude/skills/definition-actions/SKILL.md +386 -0
- data/.claude/skills/definition-fields/SKILL.md +474 -0
- data/.claude/skills/definition-query/SKILL.md +334 -0
- data/.claude/skills/forms/SKILL.md +439 -0
- data/.claude/skills/installation/SKILL.md +300 -0
- data/.claude/skills/interaction/SKILL.md +382 -0
- data/.claude/skills/model/SKILL.md +267 -0
- data/.claude/skills/model-features/SKILL.md +286 -0
- data/.claude/skills/nested-resources/SKILL.md +274 -0
- data/.claude/skills/package/SKILL.md +191 -0
- data/.claude/skills/policy/SKILL.md +352 -0
- data/.claude/skills/portal/SKILL.md +400 -0
- data/.claude/skills/resource/SKILL.md +281 -0
- data/.claude/skills/rodauth/SKILL.md +452 -0
- data/.claude/skills/views/SKILL.md +563 -0
- data/Appraisals +46 -4
- data/CHANGELOG.md +32 -1
- data/app/assets/plutonium.css +2 -2
- data/config/brakeman.ignore +239 -0
- data/config/initializers/action_policy.rb +1 -1
- data/docs/.vitepress/config.ts +132 -47
- data/docs/concepts/architecture.md +226 -0
- data/docs/concepts/auto-detection.md +254 -0
- data/docs/concepts/index.md +61 -0
- data/docs/concepts/packages-portals.md +304 -0
- data/docs/concepts/resources.md +224 -0
- data/docs/cookbook/blog.md +412 -0
- data/docs/cookbook/index.md +289 -0
- data/docs/cookbook/saas.md +481 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +146 -0
- data/docs/getting-started/tutorial/01-setup.md +118 -0
- data/docs/getting-started/tutorial/02-first-resource.md +180 -0
- data/docs/getting-started/tutorial/03-authentication.md +246 -0
- data/docs/getting-started/tutorial/04-authorization.md +170 -0
- data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
- data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
- data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
- data/docs/getting-started/tutorial/index.md +64 -0
- data/docs/guides/adding-resources.md +420 -0
- data/docs/guides/authentication.md +551 -0
- data/docs/guides/authorization.md +468 -0
- data/docs/guides/creating-packages.md +380 -0
- data/docs/guides/custom-actions.md +523 -0
- data/docs/guides/index.md +45 -0
- data/docs/guides/multi-tenancy.md +302 -0
- data/docs/guides/nested-resources.md +411 -0
- data/docs/guides/search-filtering.md +266 -0
- data/docs/guides/theming.md +321 -0
- data/docs/index.md +67 -26
- data/docs/public/CLAUDE.md +64 -21
- data/docs/reference/assets/index.md +496 -0
- data/docs/reference/controller/index.md +363 -0
- data/docs/reference/definition/actions.md +400 -0
- data/docs/reference/definition/fields.md +350 -0
- data/docs/reference/definition/index.md +252 -0
- data/docs/reference/definition/query.md +342 -0
- data/docs/reference/generators/index.md +469 -0
- data/docs/reference/index.md +49 -0
- data/docs/reference/interaction/index.md +445 -0
- data/docs/reference/model/features.md +248 -0
- data/docs/reference/model/index.md +219 -0
- data/docs/reference/policy/index.md +385 -0
- data/docs/reference/portal/index.md +382 -0
- data/docs/reference/views/forms.md +396 -0
- data/docs/reference/views/index.md +479 -0
- data/gemfiles/rails_7.gemfile +9 -2
- data/gemfiles/rails_7.gemfile.lock +146 -111
- data/gemfiles/rails_8.0.gemfile +20 -0
- data/gemfiles/rails_8.0.gemfile.lock +417 -0
- data/gemfiles/rails_8.1.gemfile +20 -0
- data/gemfiles/rails_8.1.gemfile.lock +419 -0
- data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
- data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
- data/lib/generators/pu/pkg/portal/USAGE +65 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
- data/lib/generators/pu/res/conn/USAGE +71 -0
- data/lib/generators/pu/res/model/USAGE +106 -110
- data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
- data/lib/generators/pu/res/scaffold/USAGE +85 -0
- data/lib/generators/pu/rodauth/install_generator.rb +2 -6
- data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
- data/lib/generators/pu/skills/sync/USAGE +14 -0
- data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
- data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
- data/lib/plutonium/core/controller.rb +2 -2
- data/lib/plutonium/interaction/base.rb +1 -0
- data/lib/plutonium/package/engine.rb +2 -2
- data/lib/plutonium/query/adhoc_block.rb +6 -2
- data/lib/plutonium/query/model_scope.rb +1 -1
- data/lib/plutonium/railtie.rb +4 -0
- data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
- data/lib/plutonium/resource/query_object.rb +38 -8
- data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +19 -4
- data/package.json +1 -1
- metadata +76 -39
- data/brakeman.ignore +0 -28
- data/docs/api-examples.md +0 -49
- data/docs/guide/claude-code-guide.md +0 -74
- data/docs/guide/deep-dive/authorization.md +0 -189
- data/docs/guide/deep-dive/multitenancy.md +0 -256
- data/docs/guide/deep-dive/resources.md +0 -390
- data/docs/guide/getting-started/01-installation.md +0 -165
- data/docs/guide/index.md +0 -28
- data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
- data/docs/guide/introduction/02-core-concepts.md +0 -440
- data/docs/guide/tutorial/01-project-setup.md +0 -75
- data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
- data/docs/guide/tutorial/03-defining-resources.md +0 -90
- data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
- data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
- data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
- data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
- data/docs/markdown-examples.md +0 -85
- data/docs/modules/action.md +0 -244
- data/docs/modules/authentication.md +0 -236
- data/docs/modules/configuration.md +0 -599
- data/docs/modules/controller.md +0 -443
- data/docs/modules/core.md +0 -316
- data/docs/modules/definition.md +0 -1308
- data/docs/modules/display.md +0 -759
- data/docs/modules/form.md +0 -495
- data/docs/modules/generator.md +0 -400
- data/docs/modules/index.md +0 -167
- data/docs/modules/interaction.md +0 -642
- data/docs/modules/package.md +0 -151
- data/docs/modules/policy.md +0 -176
- data/docs/modules/portal.md +0 -710
- data/docs/modules/query.md +0 -297
- data/docs/modules/resource_record.md +0 -618
- data/docs/modules/routing.md +0 -690
- data/docs/modules/table.md +0 -301
- data/docs/modules/ui.md +0 -631
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
# Views Reference
|
|
2
|
+
|
|
3
|
+
Complete reference for UI customization with Phlex components.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Plutonium uses [Phlex](https://www.phlex.fun/) for all view components. Customization happens through:
|
|
8
|
+
- Definition nested classes (pages, forms, tables, displays)
|
|
9
|
+
- Page hooks for injecting content
|
|
10
|
+
- Custom Phlex components
|
|
11
|
+
- ERB view overrides
|
|
12
|
+
|
|
13
|
+
## Architecture
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Definition
|
|
17
|
+
├── IndexPage → renders Table
|
|
18
|
+
├── ShowPage → renders Display
|
|
19
|
+
├── NewPage → renders Form
|
|
20
|
+
├── EditPage → renders Form
|
|
21
|
+
└── InteractiveActionPage → renders Interaction Form
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Page Configuration
|
|
25
|
+
|
|
26
|
+
Set titles and descriptions in definitions:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
class PostDefinition < ResourceDefinition
|
|
30
|
+
# Page titles
|
|
31
|
+
index_page_title "Blog Posts"
|
|
32
|
+
index_page_description "Manage all published articles"
|
|
33
|
+
|
|
34
|
+
show_page_title "Article Details"
|
|
35
|
+
new_page_title "Write New Article"
|
|
36
|
+
edit_page_title "Edit Article"
|
|
37
|
+
|
|
38
|
+
# Breadcrumbs
|
|
39
|
+
breadcrumbs true # Global default
|
|
40
|
+
index_page_breadcrumbs false # Per-page override
|
|
41
|
+
show_page_breadcrumbs true
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Custom Page Classes
|
|
46
|
+
|
|
47
|
+
Override page rendering by defining nested classes in your definition:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
class PostDefinition < ResourceDefinition
|
|
51
|
+
class ShowPage < ShowPage
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# Custom title logic
|
|
55
|
+
def page_title
|
|
56
|
+
"#{object.title} - #{object.author.name}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Add content before the main area
|
|
60
|
+
def render_before_content
|
|
61
|
+
div(class: "alert alert-info") {
|
|
62
|
+
"This post has #{object.comments.count} comments"
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Add content after
|
|
67
|
+
def render_after_content
|
|
68
|
+
render RelatedPostsComponent.new(post: object)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Override the toolbar
|
|
72
|
+
def render_toolbar
|
|
73
|
+
div(class: "flex gap-2") {
|
|
74
|
+
button(class: "btn") { "Preview" }
|
|
75
|
+
button(class: "btn btn-primary") { "Publish" }
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Page Hooks
|
|
83
|
+
|
|
84
|
+
All pages inherit these customization hooks:
|
|
85
|
+
|
|
86
|
+
| Hook | Purpose |
|
|
87
|
+
|------|---------|
|
|
88
|
+
| `render_before_header` | Before entire header section |
|
|
89
|
+
| `render_after_header` | After entire header section |
|
|
90
|
+
| `render_before_breadcrumbs` | Before breadcrumbs |
|
|
91
|
+
| `render_after_breadcrumbs` | After breadcrumbs |
|
|
92
|
+
| `render_before_page_header` | Before title/actions |
|
|
93
|
+
| `render_after_page_header` | After title/actions |
|
|
94
|
+
| `render_before_toolbar` | Before toolbar |
|
|
95
|
+
| `render_after_toolbar` | After toolbar |
|
|
96
|
+
| `render_before_content` | Before main content |
|
|
97
|
+
| `render_after_content` | After main content |
|
|
98
|
+
| `render_before_footer` | Before footer |
|
|
99
|
+
| `render_after_footer` | After footer |
|
|
100
|
+
|
|
101
|
+
## Form Customization
|
|
102
|
+
|
|
103
|
+
Override form rendering in your definition:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
class PostDefinition < ResourceDefinition
|
|
107
|
+
class Form < Form
|
|
108
|
+
def form_template
|
|
109
|
+
# Custom layout with sections
|
|
110
|
+
div(class: "grid grid-cols-2 gap-6") {
|
|
111
|
+
div {
|
|
112
|
+
h3(class: "text-lg font-medium") { "Basic Info" }
|
|
113
|
+
render_resource_field :title
|
|
114
|
+
render_resource_field :slug
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
div {
|
|
118
|
+
h3(class: "text-lg font-medium") { "Content" }
|
|
119
|
+
render_resource_field :content
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
div(class: "mt-6") {
|
|
124
|
+
h3(class: "text-lg font-medium") { "Publishing" }
|
|
125
|
+
render_resource_field :published_at
|
|
126
|
+
render_resource_field :category
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
render_actions
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Form Methods
|
|
136
|
+
|
|
137
|
+
| Method | Purpose |
|
|
138
|
+
|--------|---------|
|
|
139
|
+
| `render_fields` | Render all permitted fields |
|
|
140
|
+
| `render_resource_field(name)` | Render a single field |
|
|
141
|
+
| `render_actions` | Render submit buttons |
|
|
142
|
+
| `record` / `object` | The form object |
|
|
143
|
+
| `resource_fields` | List of permitted field names |
|
|
144
|
+
| `resource_definition` | The definition instance |
|
|
145
|
+
|
|
146
|
+
## Display Customization
|
|
147
|
+
|
|
148
|
+
Override show page detail rendering:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
class PostDefinition < ResourceDefinition
|
|
152
|
+
class Display < Display
|
|
153
|
+
def display_template
|
|
154
|
+
# Hero section
|
|
155
|
+
div(class: "bg-gradient-to-r from-blue-500 to-purple-600 p-8 rounded-lg text-white mb-6") {
|
|
156
|
+
h1(class: "text-3xl font-bold") { object.title }
|
|
157
|
+
p(class: "mt-2 opacity-90") { object.excerpt }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# Main content
|
|
161
|
+
Block do
|
|
162
|
+
fields_wrapper do
|
|
163
|
+
render_resource_field :author
|
|
164
|
+
render_resource_field :published_at
|
|
165
|
+
render_resource_field :category
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Full-width content
|
|
170
|
+
Block do
|
|
171
|
+
div(class: "prose max-w-none") {
|
|
172
|
+
raw object.content
|
|
173
|
+
}
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Associations (tabs)
|
|
177
|
+
render_associations if present_associations?
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Display Methods
|
|
184
|
+
|
|
185
|
+
| Method | Purpose |
|
|
186
|
+
|--------|---------|
|
|
187
|
+
| `render_fields` | Render all permitted fields in a block |
|
|
188
|
+
| `render_resource_field(name)` | Render single field |
|
|
189
|
+
| `render_associations` | Render association tabs |
|
|
190
|
+
| `object` | The record being displayed |
|
|
191
|
+
| `resource_fields` | List of permitted field names |
|
|
192
|
+
| `resource_associations` | List of permitted associations |
|
|
193
|
+
|
|
194
|
+
## Table Customization
|
|
195
|
+
|
|
196
|
+
Override index page table:
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
class PostDefinition < ResourceDefinition
|
|
200
|
+
class Table < Table
|
|
201
|
+
def view_template
|
|
202
|
+
render_search_bar
|
|
203
|
+
render_scopes_bar
|
|
204
|
+
|
|
205
|
+
if collection.empty?
|
|
206
|
+
render_empty_card
|
|
207
|
+
else
|
|
208
|
+
# Custom card grid instead of table
|
|
209
|
+
div(class: "grid grid-cols-3 gap-4") {
|
|
210
|
+
collection.each do |post|
|
|
211
|
+
render PostCardComponent.new(post:)
|
|
212
|
+
end
|
|
213
|
+
}
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
render_footer
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Table Methods
|
|
223
|
+
|
|
224
|
+
| Method | Purpose |
|
|
225
|
+
|--------|---------|
|
|
226
|
+
| `render_search_bar` | Search input |
|
|
227
|
+
| `render_scopes_bar` | Scope tabs |
|
|
228
|
+
| `render_table` | Default table |
|
|
229
|
+
| `render_empty_card` | Empty state |
|
|
230
|
+
| `render_footer` | Pagination |
|
|
231
|
+
| `collection` | The paginated records |
|
|
232
|
+
| `resource_fields` | Column field names |
|
|
233
|
+
|
|
234
|
+
## Component Kit
|
|
235
|
+
|
|
236
|
+
Plutonium provides shorthand methods for common components:
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
class MyPage < Plutonium::UI::Page::Base
|
|
240
|
+
def view_template
|
|
241
|
+
# These are automatically rendered
|
|
242
|
+
PageHeader(title: "Dashboard")
|
|
243
|
+
|
|
244
|
+
Panel(class: "mt-4") {
|
|
245
|
+
p { "Content here" }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
Block {
|
|
249
|
+
TabList(items: tabs)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
EmptyCard("No items found")
|
|
253
|
+
|
|
254
|
+
ActionButton(action, url: "/posts/new")
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Available kit methods:
|
|
260
|
+
|
|
261
|
+
| Method | Purpose |
|
|
262
|
+
|--------|---------|
|
|
263
|
+
| `Breadcrumbs()` | Navigation breadcrumbs |
|
|
264
|
+
| `PageHeader(title:, description:, actions:)` | Page header with actions |
|
|
265
|
+
| `Panel(**attrs)` | Content panel |
|
|
266
|
+
| `Block(**attrs)` | Content block |
|
|
267
|
+
| `TabList(items:)` | Tab navigation |
|
|
268
|
+
| `EmptyCard(message)` | Empty state card |
|
|
269
|
+
| `ActionButton(action, url:)` | Action button |
|
|
270
|
+
| `DynaFrameHost()` / `DynaFrameContent()` | Turbo frame helpers |
|
|
271
|
+
| `TableSearchBar()` | Search bar for tables |
|
|
272
|
+
| `TableScopesBar()` | Scope tabs for tables |
|
|
273
|
+
| `TableInfo(pagy)` | Pagination info |
|
|
274
|
+
| `TablePagination(pagy)` | Pagination links |
|
|
275
|
+
| `FrameNavigatorPanel(title:, src:, panel_id:)` | Frame navigation panel |
|
|
276
|
+
|
|
277
|
+
## Custom Components
|
|
278
|
+
|
|
279
|
+
### Creating a Phlex Component
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
# app/components/post_card_component.rb
|
|
283
|
+
class PostCardComponent < Plutonium::UI::Component::Base
|
|
284
|
+
def initialize(post:)
|
|
285
|
+
@post = post
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def view_template
|
|
289
|
+
div(class: "bg-white rounded-lg shadow p-4") {
|
|
290
|
+
h3(class: "font-bold") { @post.title }
|
|
291
|
+
p(class: "text-gray-600 mt-2") { @post.excerpt }
|
|
292
|
+
|
|
293
|
+
div(class: "mt-4 flex justify-between items-center") {
|
|
294
|
+
span(class: "text-sm text-gray-500") { @post.published_at&.strftime("%B %d, %Y") }
|
|
295
|
+
a(href: resource_url_for(@post), class: "text-blue-600") { "Read more" }
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Using Components in Definitions
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
class PostDefinition < ResourceDefinition
|
|
306
|
+
# Custom display component
|
|
307
|
+
display :status, as: StatusBadgeComponent
|
|
308
|
+
|
|
309
|
+
# Custom input component
|
|
310
|
+
input :color, as: ColorPickerComponent
|
|
311
|
+
|
|
312
|
+
# Block with component
|
|
313
|
+
display :metrics do |field|
|
|
314
|
+
MetricsChartComponent.new(data: field.value)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Layout Customization
|
|
320
|
+
|
|
321
|
+
### Custom Layout Class
|
|
322
|
+
|
|
323
|
+
```ruby
|
|
324
|
+
# packages/admin_portal/app/views/layouts/admin_portal/resource_layout.rb
|
|
325
|
+
module AdminPortal
|
|
326
|
+
class ResourceLayout < Plutonium::UI::Layout::ResourceLayout
|
|
327
|
+
private
|
|
328
|
+
|
|
329
|
+
# Custom main content area classes
|
|
330
|
+
def main_attributes
|
|
331
|
+
mix(super, { class: "pt-20 lg:ml-64" })
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Add custom header content
|
|
335
|
+
def render_before_main
|
|
336
|
+
super
|
|
337
|
+
render AnnouncementBanner.new if Announcement.active.any?
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Custom scripts
|
|
341
|
+
def render_body_scripts
|
|
342
|
+
super
|
|
343
|
+
script(src: "/custom-analytics.js")
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Layout Hooks
|
|
350
|
+
|
|
351
|
+
| Hook | Purpose |
|
|
352
|
+
|------|---------|
|
|
353
|
+
| `render_before_main` | Before main content area |
|
|
354
|
+
| `render_after_main` | After main (modals, etc.) |
|
|
355
|
+
| `render_head` | HTML head section |
|
|
356
|
+
| `render_title` | Page title tag |
|
|
357
|
+
| `render_assets` | CSS/JS assets |
|
|
358
|
+
| `render_body_scripts` | Scripts at end of body |
|
|
359
|
+
|
|
360
|
+
## Custom ERB Views
|
|
361
|
+
|
|
362
|
+
For complete control, create custom ERB view files:
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
# Main app (for a PostsController)
|
|
366
|
+
app/views/posts/index.html.erb
|
|
367
|
+
app/views/posts/show.html.erb
|
|
368
|
+
|
|
369
|
+
# Portal-specific
|
|
370
|
+
packages/admin_portal/app/views/admin_portal/posts/show.html.erb
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The default views render the page class:
|
|
374
|
+
|
|
375
|
+
```erb
|
|
376
|
+
<%# app/views/resource/show.html.erb %>
|
|
377
|
+
<%= render current_definition.show_page_class.new %>
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Custom view example:
|
|
381
|
+
|
|
382
|
+
```erb
|
|
383
|
+
<%# app/views/posts/show.html.erb %>
|
|
384
|
+
<div class="max-w-4xl mx-auto">
|
|
385
|
+
<article class="prose lg:prose-xl">
|
|
386
|
+
<h1><%= resource_record!.title %></h1>
|
|
387
|
+
<%= raw resource_record!.content %>
|
|
388
|
+
</article>
|
|
389
|
+
|
|
390
|
+
<div class="mt-8">
|
|
391
|
+
<%= link_to "Edit", resource_url_for(resource_record!, action: :edit), class: "btn" %>
|
|
392
|
+
<%= link_to "Back", resource_url_for(Post), class: "btn" %>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Available Context
|
|
398
|
+
|
|
399
|
+
### Resource Methods
|
|
400
|
+
|
|
401
|
+
| Method | Description |
|
|
402
|
+
|--------|-------------|
|
|
403
|
+
| `resource_class` | The model class (e.g., `Post`) |
|
|
404
|
+
| `resource_record!` | Current record (raises if not found) |
|
|
405
|
+
| `resource_record?` | Current record (nil if not found) |
|
|
406
|
+
| `current_parent` | Parent record for nested routes |
|
|
407
|
+
| `current_scoped_entity` | Entity for multi-tenant portals |
|
|
408
|
+
|
|
409
|
+
### Definition & Policy
|
|
410
|
+
|
|
411
|
+
| Method | Description |
|
|
412
|
+
|--------|-------------|
|
|
413
|
+
| `current_definition` | Definition instance for current resource |
|
|
414
|
+
| `current_policy` | Policy instance for current record |
|
|
415
|
+
| `current_authorized_scope` | Scoped collection user can access |
|
|
416
|
+
|
|
417
|
+
### URL Helpers
|
|
418
|
+
|
|
419
|
+
| Method | Description |
|
|
420
|
+
|--------|-------------|
|
|
421
|
+
| `resource_url_for(record)` | URL for a record |
|
|
422
|
+
| `resource_url_for(record, action: :edit)` | Action URL for record |
|
|
423
|
+
| `resource_url_for(Model)` | Index URL for model |
|
|
424
|
+
| `resource_url_for(Model, action: :new)` | New URL for model |
|
|
425
|
+
| `resource_url_for(record, parent: parent)` | Nested resource URL |
|
|
426
|
+
|
|
427
|
+
### Display Helpers
|
|
428
|
+
|
|
429
|
+
| Method | Description |
|
|
430
|
+
|--------|-------------|
|
|
431
|
+
| `display_name_of(record)` | Human-readable name for record |
|
|
432
|
+
| `resource_name(klass)` | Singular model name |
|
|
433
|
+
| `resource_name_plural(klass)` | Plural model name |
|
|
434
|
+
|
|
435
|
+
### In Phlex Components
|
|
436
|
+
|
|
437
|
+
```ruby
|
|
438
|
+
class MyComponent < Plutonium::UI::Component::Base
|
|
439
|
+
def view_template
|
|
440
|
+
# Plutonium methods work directly
|
|
441
|
+
current_user
|
|
442
|
+
resource_record!
|
|
443
|
+
resource_url_for(@post)
|
|
444
|
+
|
|
445
|
+
# Rails helpers via helpers proxy
|
|
446
|
+
helpers.link_to(...)
|
|
447
|
+
helpers.image_tag(...)
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## Portal-Specific Views
|
|
453
|
+
|
|
454
|
+
Each portal can override views:
|
|
455
|
+
|
|
456
|
+
```ruby
|
|
457
|
+
# Base definition
|
|
458
|
+
class PostDefinition < ResourceDefinition
|
|
459
|
+
class ShowPage < ShowPage
|
|
460
|
+
# Default behavior
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Admin portal override
|
|
465
|
+
class AdminPortal::PostDefinition < ::PostDefinition
|
|
466
|
+
class ShowPage < ShowPage # Inherits from ::PostDefinition::ShowPage
|
|
467
|
+
def render_after_content
|
|
468
|
+
super
|
|
469
|
+
render AdminOnlySection.new(post: object)
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## Related
|
|
476
|
+
|
|
477
|
+
- [Forms Reference](./forms) - Custom form templates and field builders
|
|
478
|
+
- [Theming Guide](/guides/theming) - TailwindCSS and styling
|
|
479
|
+
- [Fields Reference](/reference/definition/fields) - Field configuration
|
data/gemfiles/rails_7.gemfile
CHANGED
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
source "https://rubygems.org"
|
|
4
4
|
|
|
5
|
-
gem "rails", "~> 7.
|
|
6
|
-
gem "sqlite3"
|
|
5
|
+
gem "rails", "~> 7.2"
|
|
6
|
+
gem "sqlite3"
|
|
7
7
|
gem "puma", ">= 5.0"
|
|
8
8
|
gem "importmap-rails"
|
|
9
9
|
gem "turbo-rails"
|
|
10
10
|
gem "stimulus-rails"
|
|
11
|
+
gem "propshaft"
|
|
12
|
+
gem "rodauth-rails"
|
|
13
|
+
gem "sequel-activerecord_connection"
|
|
14
|
+
gem "tilt"
|
|
15
|
+
gem "bcrypt"
|
|
16
|
+
gem "rotp"
|
|
17
|
+
gem "rqrcode"
|
|
11
18
|
gem "tzinfo-data", platforms: [:windows, :jruby]
|
|
12
19
|
|
|
13
20
|
gemspec path: "../"
|