plutonium 0.42.0 → 0.43.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium-controller/SKILL.md +38 -1
  3. data/.claude/skills/plutonium-definition/SKILL.md +14 -0
  4. data/.claude/skills/plutonium-forms/SKILL.md +16 -1
  5. data/.claude/skills/plutonium-profile/SKILL.md +276 -0
  6. data/.claude/skills/plutonium-views/SKILL.md +23 -1
  7. data/CHANGELOG.md +42 -0
  8. data/app/assets/plutonium.css +2 -2
  9. data/app/views/plutonium/_resource_header.html.erb +6 -27
  10. data/app/views/plutonium/_resource_sidebar.html.erb +1 -2
  11. data/app/views/resource/_resource_details.rabl +3 -2
  12. data/app/views/resource/index.rabl +3 -2
  13. data/app/views/resource/show.rabl +3 -2
  14. data/docs/guides/user-profile.md +322 -0
  15. data/docs/reference/controller/index.md +38 -1
  16. data/docs/reference/definition/index.md +16 -0
  17. data/docs/reference/views/forms.md +15 -0
  18. data/docs/reference/views/index.md +23 -1
  19. data/gemfiles/rails_7.gemfile.lock +1 -1
  20. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  21. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  22. data/lib/generators/pu/core/assets/assets_generator.rb +12 -0
  23. data/lib/generators/pu/core/install/templates/app/controllers/resource_controller.rb.tt +11 -0
  24. data/lib/generators/pu/core/typespec/templates/common.tsp.tt +95 -0
  25. data/lib/generators/pu/core/typespec/templates/main.tsp.tt +27 -0
  26. data/lib/generators/pu/core/typespec/templates/main_multi.tsp.tt +25 -0
  27. data/lib/generators/pu/core/typespec/templates/model.tsp.tt +226 -0
  28. data/lib/generators/pu/core/typespec/typespec_generator.rb +342 -0
  29. data/lib/generators/pu/invites/USAGE +0 -1
  30. data/lib/generators/pu/invites/install_generator.rb +62 -15
  31. data/lib/generators/pu/invites/templates/db/migrate/create_user_invites.rb.tt +2 -2
  32. data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +2 -0
  33. data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/welcome_controller.rb.tt +1 -0
  34. data/lib/generators/pu/invites/templates/packages/invites/app/models/invites/user_invite.rb.tt +5 -5
  35. data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invitations/signup.html.erb.tt +4 -4
  36. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +1 -1
  37. data/lib/generators/pu/lib/plutonium_generators/generator.rb +29 -0
  38. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +6 -23
  39. data/lib/generators/pu/pkg/portal/portal_generator.rb +5 -1
  40. data/lib/generators/pu/profile/USAGE +59 -0
  41. data/lib/generators/pu/profile/concerns/profile_arguments.rb +27 -0
  42. data/lib/generators/pu/profile/conn/USAGE +33 -0
  43. data/lib/generators/pu/profile/conn_generator.rb +167 -0
  44. data/lib/generators/pu/profile/install_generator.rb +119 -0
  45. data/lib/generators/pu/profile/setup/USAGE +42 -0
  46. data/lib/generators/pu/profile/setup_generator.rb +73 -0
  47. data/lib/generators/pu/rodauth/account_generator.rb +2 -4
  48. data/lib/generators/pu/rodauth/install_generator.rb +2 -2
  49. data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +3 -0
  50. data/lib/generators/pu/saas/api_client_generator.rb +0 -2
  51. data/lib/generators/pu/saas/membership_generator.rb +68 -19
  52. data/lib/generators/pu/saas/setup_generator.rb +7 -2
  53. data/lib/generators/pu/saas/user_generator.rb +0 -2
  54. data/lib/plutonium/auth/rodauth.rb +8 -0
  55. data/lib/plutonium/core/controller.rb +7 -4
  56. data/lib/plutonium/core/controllers/authorizable.rb +5 -1
  57. data/lib/plutonium/definition/base.rb +7 -0
  58. data/lib/plutonium/helpers/display_helper.rb +6 -0
  59. data/lib/plutonium/profile/security_section.rb +118 -0
  60. data/lib/plutonium/resource/controller.rb +17 -7
  61. data/lib/plutonium/resource/controllers/interactive_actions.rb +11 -25
  62. data/lib/plutonium/resource/controllers/presentable.rb +46 -3
  63. data/lib/plutonium/resource/record/associated_with.rb +7 -1
  64. data/lib/plutonium/routing/mapper_extensions.rb +18 -18
  65. data/lib/plutonium/routing/route_set_extensions.rb +23 -2
  66. data/lib/plutonium/ui/breadcrumbs.rb +111 -131
  67. data/lib/plutonium/ui/dyna_frame/content.rb +12 -2
  68. data/lib/plutonium/ui/form/resource.rb +26 -19
  69. data/lib/plutonium/ui/page/base.rb +14 -14
  70. data/lib/plutonium/ui/table/components/scopes_bar.rb +2 -74
  71. data/lib/plutonium/ui/table/components/selection_column.rb +6 -2
  72. data/lib/plutonium/ui/table/resource.rb +3 -2
  73. data/lib/plutonium/version.rb +1 -1
  74. data/lib/tasks/release.rake +6 -6
  75. data/package.json +1 -1
  76. metadata +17 -3
  77. data/lib/generators/pu/rodauth/concerns/gem_helpers.rb +0 -19
@@ -44,16 +44,28 @@ module Plutonium
44
44
  end
45
45
  end
46
46
 
47
+ # Draws routes wrapped in entity scope when using path-based entity scoping.
48
+ #
49
+ # @param scope_params [Hash, nil] Scope params from RouteSetExtensions, or nil if no scoping
50
+ # @param block [Proc] The block containing route definitions.
51
+ # @return [void]
52
+ def draw_routes_with_entity_scope(scope_params, &block)
53
+ if scope_params
54
+ scope scope_params[:name], **scope_params[:options] do
55
+ instance_exec(&block)
56
+ materialize_resource_routes
57
+ end
58
+ else
59
+ instance_exec(&block)
60
+ materialize_resource_routes
61
+ end
62
+ end
63
+
47
64
  # Materializes all registered resource routes.
48
65
  #
49
66
  # @return [void]
50
67
  def materialize_resource_routes
51
- engine = route_set.engine
52
- scope_params = determine_scope_params(engine)
53
-
54
- scope scope_params[:name], scope_params[:options] do
55
- concerns resource_route_concern_names.sort
56
- end
68
+ concerns resource_route_concern_names.sort
57
69
  end
58
70
 
59
71
  # @return [ActionDispatch::Routing::RouteSet] The current route set.
@@ -149,18 +161,6 @@ module Plutonium
149
161
  as: :commit_interactive_resource_action
150
162
  end
151
163
  end
152
-
153
- # Determines the scope parameters based on the engine configuration.
154
- #
155
- # @param engine [Class] The current engine.
156
- # @return [Hash] Scope name and options.
157
- def determine_scope_params(engine)
158
- scoped_entity_param_key = engine.scoped_entity_param_key if engine.scoped_entity_strategy == :path
159
- {
160
- name: scoped_entity_param_key.present? ? ":#{scoped_entity_param_key}" : "",
161
- options: scoped_entity_param_key.present? ? {as: scoped_entity_param_key} : {}
162
- }
163
- end
164
164
  end
165
165
  end
166
166
  end
@@ -34,11 +34,11 @@ module Plutonium
34
34
  # @yield Executes the given block in the context of route drawing.
35
35
  def draw(&block)
36
36
  if self.class.supported_engine?(engine)
37
+ scope_params = entity_scope_params_for_path_strategy
37
38
  ActiveSupport::Notifications.instrument("plutonium.resource_routes.draw", app: engine.to_s) do
38
39
  super do
39
40
  setup_shared_resource_concerns
40
- instance_exec(&block)
41
- materialize_resource_routes
41
+ draw_routes_with_entity_scope(scope_params, &block)
42
42
  end
43
43
  end
44
44
  else
@@ -46,6 +46,19 @@ module Plutonium
46
46
  end
47
47
  end
48
48
 
49
+ # Determines entity scope parameters for path-based scoping.
50
+ #
51
+ # @return [Hash, nil] Scope params if path-based scoping is enabled, nil otherwise
52
+ def entity_scope_params_for_path_strategy
53
+ return nil unless engine.scoped_entity_strategy == :path
54
+
55
+ param_key = engine.scoped_entity_param_key
56
+ {
57
+ name: ":#{param_key}",
58
+ options: {as: param_key}
59
+ }
60
+ end
61
+
49
62
  # Registers a resource for routing.
50
63
  #
51
64
  # @param resource [Class] The resource class to be registered.
@@ -75,6 +88,14 @@ module Plutonium
75
88
  resource_route_config_lookup.slice(*routes).values
76
89
  end
77
90
 
91
+ # Checks if a resource is registered as a singular route.
92
+ #
93
+ # @param route_key [String] The route key (e.g., "users" or "users/profiles")
94
+ # @return [Boolean] true if the resource is a singular route, false otherwise
95
+ def singular_resource_route?(route_key)
96
+ resource_route_config_for(route_key)[0]&.[](:route_type) == :resource
97
+ end
98
+
78
99
  # Returns the current engine for the routes.
79
100
  #
80
101
  # @return [Class] The engine class (Rails application or custom engine).
@@ -12,147 +12,127 @@ module Plutonium
12
12
  ol(
13
13
  class: "inline-flex items-center gap-1 md:gap-2"
14
14
  ) do
15
- # Dashboard
16
- li(class: "inline-flex items-center") do
17
- a(
18
- href: root_path,
19
- class: "inline-flex items-center text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 transition-colors"
20
- ) do
21
- svg(
22
- class: "w-3 h-3 me-2.5",
23
- aria_hidden: "true",
24
- xmlns: "http://www.w3.org/2000/svg",
25
- fill: "currentColor",
26
- viewbox: "0 0 20 20"
27
- ) do |s|
28
- s.path(
29
- d:
30
- "m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z"
31
- )
32
- end
33
- plain " Dashboard "
34
- end
35
- end
15
+ render_dashboard_link
16
+ render_parent_breadcrumbs if current_parent.present?
17
+ render_resource_breadcrumbs if resource_record?
18
+ render_trailing_separator
19
+ end
20
+ end
21
+ end
36
22
 
37
- # Parent
38
- if current_parent.present?
39
- # Parent Resource
40
- li(class: "flex items-center") do
41
- svg(
42
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
43
- aria_hidden: "true",
44
- xmlns: "http://www.w3.org/2000/svg",
45
- fill: "none",
46
- viewbox: "0 0 6 10"
47
- ) do |s|
48
- s.path(
49
- stroke: "currentColor",
50
- stroke_linecap: "round",
51
- stroke_linejoin: "round",
52
- stroke_width: "2",
53
- d: "m1 9 4-4-4-4"
54
- )
55
- end
56
- link_to resource_name_plural(current_parent.class),
57
- resource_url_for(current_parent.class, parent: nil),
58
- class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
59
- end
23
+ private
60
24
 
61
- # Parent Itself
62
- li(class: "flex items-center") do
63
- svg(
64
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
65
- aria_hidden: "true",
66
- xmlns: "http://www.w3.org/2000/svg",
67
- fill: "none",
68
- viewbox: "0 0 6 10"
69
- ) do |s|
70
- s.path(
71
- stroke: "currentColor",
72
- stroke_linecap: "round",
73
- stroke_linejoin: "round",
74
- stroke_width: "2",
75
- d: "m1 9 4-4-4-4"
76
- )
77
- end
78
- link_to display_name_of(current_parent),
79
- resource_url_for(current_parent, parent: nil),
80
- class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
81
- end
25
+ def render_dashboard_link
26
+ li(class: "inline-flex items-center") do
27
+ a(
28
+ href: root_path,
29
+ class: "inline-flex items-center text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 transition-colors"
30
+ ) do
31
+ svg(
32
+ class: "w-3 h-3 me-2.5",
33
+ aria_hidden: "true",
34
+ xmlns: "http://www.w3.org/2000/svg",
35
+ fill: "currentColor",
36
+ viewbox: "0 0 20 20"
37
+ ) do |s|
38
+ s.path(
39
+ d:
40
+ "m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z"
41
+ )
82
42
  end
43
+ plain " Dashboard "
44
+ end
45
+ end
46
+ end
83
47
 
84
- # Record
85
- if resource_record?
86
- unless current_engine.routes.resource_route_config_lookup[resource_class.model_name.plural][:route_type] == :resource
87
- # Record Resource
88
- li(class: "flex items-center") do
89
- svg(
90
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
91
- aria_hidden: "true",
92
- xmlns: "http://www.w3.org/2000/svg",
93
- fill: "none",
94
- viewbox: "0 0 6 10"
95
- ) do |s|
96
- s.path(
97
- stroke: "currentColor",
98
- stroke_linecap: "round",
99
- stroke_linejoin: "round",
100
- stroke_width: "2",
101
- d: "m1 9 4-4-4-4"
102
- )
103
- end
104
- link_to nestable_resource_name_plural(resource_class),
105
- resource_url_for(resource_class),
106
- class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
107
- end
108
- end
48
+ def render_parent_breadcrumbs
49
+ # Parent Resource
50
+ render_breadcrumb_item do
51
+ link_to resource_name_plural(current_parent.class),
52
+ resource_url_for(current_parent.class, parent: nil),
53
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
54
+ end
109
55
 
110
- # Record Itself
111
- if resource_record!.persisted? && action_name != "show"
112
- li(class: "flex items-center") do
113
- svg(
114
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
115
- aria_hidden: "true",
116
- xmlns: "http://www.w3.org/2000/svg",
117
- fill: "none",
118
- viewbox: "0 0 6 10"
119
- ) do |s|
120
- s.path(
121
- stroke: "currentColor",
122
- stroke_linecap: "round",
123
- stroke_linejoin: "round",
124
- stroke_width: "2",
125
- d: "m1 9 4-4-4-4"
126
- )
127
- end
128
- link_to display_name_of(resource_record!),
129
- resource_url_for(resource_record!),
130
- class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
131
- end
132
- end
133
- end
56
+ # Parent Itself
57
+ render_breadcrumb_item do
58
+ link_to display_name_of(current_parent),
59
+ resource_url_for(current_parent, parent: nil),
60
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
61
+ end
62
+ end
63
+
64
+ def render_resource_breadcrumbs
65
+ is_singular_route = current_engine.routes.resource_route_config_lookup[resource_class.model_name.plural][:route_type] == :resource
66
+
67
+ if is_singular_route
68
+ render_singular_resource_breadcrumb
69
+ else
70
+ render_plural_resource_breadcrumbs
71
+ end
72
+ end
134
73
 
135
- # Trailing Caret
136
- li(class: "flex items-center") do
137
- svg(
138
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
139
- aria_hidden: "true",
140
- xmlns: "http://www.w3.org/2000/svg",
141
- fill: "none",
142
- viewbox: "0 0 6 10"
143
- ) do |s|
144
- s.path(
145
- stroke: "currentColor",
146
- stroke_linecap: "round",
147
- stroke_linejoin: "round",
148
- stroke_width: "2",
149
- d: "m1 9 4-4-4-4"
150
- )
151
- end
74
+ def render_singular_resource_breadcrumb
75
+ render_breadcrumb_item do
76
+ if resource_record!.persisted? && action_name != "show"
77
+ link_to resource_name(resource_class),
78
+ resource_url_for(resource_record!),
79
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
80
+ else
81
+ span(class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] md:ms-2") do
82
+ plain resource_name(resource_class)
152
83
  end
153
84
  end
154
85
  end
155
86
  end
87
+
88
+ def render_plural_resource_breadcrumbs
89
+ # Resource index link
90
+ render_breadcrumb_item do
91
+ link_to nestable_resource_name_plural(resource_class),
92
+ resource_url_for(resource_class),
93
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
94
+ end
95
+
96
+ # Record itself (for non-singular routes only)
97
+ return unless resource_record!.persisted? && action_name != "show"
98
+
99
+ render_breadcrumb_item do
100
+ link_to display_name_of(resource_record!),
101
+ resource_url_for(resource_record!),
102
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
103
+ end
104
+ end
105
+
106
+ def render_trailing_separator
107
+ li(class: "flex items-center") do
108
+ render_chevron_separator
109
+ end
110
+ end
111
+
112
+ def render_breadcrumb_item(&)
113
+ li(class: "flex items-center") do
114
+ render_chevron_separator
115
+ yield
116
+ end
117
+ end
118
+
119
+ def render_chevron_separator
120
+ svg(
121
+ class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
122
+ aria_hidden: "true",
123
+ xmlns: "http://www.w3.org/2000/svg",
124
+ fill: "none",
125
+ viewbox: "0 0 6 10"
126
+ ) do |s|
127
+ s.path(
128
+ stroke: "currentColor",
129
+ stroke_linecap: "round",
130
+ stroke_linejoin: "round",
131
+ stroke_width: "2",
132
+ d: "m1 9 4-4-4-4"
133
+ )
134
+ end
135
+ end
156
136
  end
157
137
  end
158
138
  end
@@ -4,16 +4,26 @@ module Plutonium
4
4
  class Content < Plutonium::UI::Component::Base
5
5
  include Phlex::Rails::Helpers::TurboFrameTag
6
6
 
7
+ def initialize(content = nil)
8
+ @content = content
9
+ end
10
+
7
11
  def view_template
8
12
  if current_turbo_frame.present?
13
+ # Frame request: render only the turbo-frame with content
9
14
  turbo_frame_tag(current_turbo_frame) do
10
15
  render partial("flash")
11
- yield
16
+ @content&.call
12
17
  end
13
18
  else
14
- yield
19
+ # Regular request: yield self so caller can call frame.render_content
20
+ yield(self)
15
21
  end
16
22
  end
23
+
24
+ def render_content
25
+ @content&.call
26
+ end
17
27
  end
18
28
  end
19
29
  end
@@ -6,14 +6,15 @@ module Plutonium
6
6
  class Resource < Base
7
7
  include Plutonium::UI::Form::Concerns::RendersNestedResourceFields
8
8
 
9
- attr_reader :resource_fields, :resource_definition
9
+ attr_reader :resource_fields, :resource_definition, :singular_resource
10
10
 
11
11
  alias_method :record, :object
12
12
 
13
- def initialize(*, resource_fields:, resource_definition:, **, &)
13
+ def initialize(*, resource_fields:, resource_definition:, singular_resource: false, **, &)
14
14
  super(*, **, &)
15
15
  @resource_fields = resource_fields
16
16
  @resource_definition = resource_definition
17
+ @singular_resource = singular_resource
17
18
  end
18
19
 
19
20
  def form_template
@@ -35,28 +36,34 @@ module Plutonium
35
36
  input name: "return_to", value: request.params[:return_to], type: :hidden, hidden: true
36
37
 
37
38
  actions_wrapper {
38
- if object.respond_to?(:new_record?)
39
- if object.new_record?
40
- button(
41
- type: :submit,
42
- name: "return_to",
43
- value: request.url,
44
- class: "px-4 py-2 bg-secondary-600 text-white rounded-md hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-secondary-500"
45
- ) { "Create and add another" }
46
- else
47
- button(
48
- type: :submit,
49
- name: "return_to",
50
- value: request.url,
51
- class: "px-4 py-2 bg-secondary-600 text-white rounded-md hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-secondary-500"
52
- ) { "Update and continue editing" }
53
- end
54
- end
39
+ render_submit_and_continue_button if show_submit_and_continue?
55
40
 
56
41
  render submit_button
57
42
  }
58
43
  end
59
44
 
45
+ def show_submit_and_continue?
46
+ return false unless object.respond_to?(:new_record?)
47
+
48
+ # Check explicit configuration first
49
+ configured = resource_definition.submit_and_continue
50
+ return configured unless configured.nil?
51
+
52
+ # Auto-detect: hide for singular resources
53
+ !singular_resource
54
+ end
55
+
56
+ def render_submit_and_continue_button
57
+ label = object.new_record? ? "Create and add another" : "Update and continue editing"
58
+
59
+ button(
60
+ type: :submit,
61
+ name: "return_to",
62
+ value: request.url,
63
+ class: "px-4 py-2 bg-secondary-600 text-white rounded-md hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-secondary-500"
64
+ ) { label }
65
+ end
66
+
60
67
  def form_action
61
68
  return @form_action unless object.present? && @form_action != false && view_context.present?
62
69
 
@@ -10,18 +10,20 @@ module Plutonium
10
10
  @page_actions = page_actions
11
11
  end
12
12
 
13
- def view_template(&)
14
- render_before_header
15
- render_header
16
- render_after_header
13
+ def view_template(&block)
14
+ DynaFrameContent(page_content(block)) do |frame|
15
+ render_before_header
16
+ render_header
17
+ render_after_header
17
18
 
18
- render_before_content
19
- render_content(&)
20
- render_after_content
19
+ render_before_content
20
+ frame.render_content
21
+ render_after_content
21
22
 
22
- render_before_footer
23
- render_footer
24
- render_after_footer
23
+ render_before_footer
24
+ render_footer
25
+ render_after_footer
26
+ end
25
27
  end
26
28
 
27
29
  private
@@ -64,10 +66,8 @@ module Plutonium
64
66
  # Implement toolbar content
65
67
  end
66
68
 
67
- def render_content(&block)
68
- block ||= proc { render_default_content }
69
-
70
- DynaFrameContent(&block)
69
+ def page_content(block)
70
+ block || proc { render_default_content }
71
71
  end
72
72
 
73
73
  def render_default_content
@@ -9,9 +9,7 @@ module Plutonium
9
9
 
10
10
  def view_template
11
11
  div(
12
- class:
13
- # "flex flex-wrap justify-between items-center gap-4 p-4 bg-white border border-gray-200 rounded-lg dark:bg-gray-800 dark:border-gray-700 mb-4"
14
- "flex flex-wrap justify-between items-center gap-4 mb-4"
12
+ class: "flex flex-wrap justify-between items-center gap-4 mb-4"
15
13
  ) do
16
14
  div(class: "flex flex-wrap items-center gap-2") do
17
15
  render_all_scope_button
@@ -19,76 +17,6 @@ module Plutonium
19
17
  render_scope_button(name)
20
18
  end
21
19
  end
22
-
23
- # div(class: "flex flex-wrap items-center gap-2") do
24
- # button(
25
- # class:
26
- # "inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-red-700 rounded-lg hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800"
27
- # ) do
28
- # svg(
29
- # class: "w-4 h-4 mr-2",
30
- # fill: "none",
31
- # stroke: "currentColor",
32
- # viewbox: "0 0 24 24",
33
- # xmlns: "http://www.w3.org/2000/svg"
34
- # ) do |s|
35
- # s.path(
36
- # stroke_linecap: "round",
37
- # stroke_linejoin: "round",
38
- # stroke_width: "2",
39
- # d:
40
- # "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
41
- # )
42
- # end
43
- # plain " Delete Selected "
44
- # end
45
- # button(
46
- # class:
47
- # "inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-yellow-700 rounded-lg hover:bg-yellow-800 focus:ring-4 focus:outline-none focus:ring-yellow-300 dark:bg-yellow-600 dark:hover:bg-yellow-700 dark:focus:ring-yellow-800"
48
- # ) do
49
- # svg(
50
- # class: "w-4 h-4 mr-2",
51
- # fill: "none",
52
- # stroke: "currentColor",
53
- # viewbox: "0 0 24 24",
54
- # xmlns: "http://www.w3.org/2000/svg"
55
- # ) do |s|
56
- # s.path(
57
- # stroke_linecap: "round",
58
- # stroke_linejoin: "round",
59
- # stroke_width: "2",
60
- # d:
61
- # "M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"
62
- # )
63
- # end
64
- # plain " Archive Selected "
65
- # end
66
- # button(
67
- # id: "dropdownActionButton",
68
- # data_dropdown_toggle: "dropdownAction",
69
- # class:
70
- # "inline-flex items-center text-gray-500 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 font-medium rounded-lg text-sm px-3 py-2 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700",
71
- # type: "button"
72
- # ) do
73
- # span(class: "sr-only") { "Action button" }
74
- # plain " More Actions "
75
- # svg(
76
- # class: "w-2.5 h-2.5 ml-2.5",
77
- # aria_hidden: "true",
78
- # xmlns: "http://www.w3.org/2000/svg",
79
- # fill: "none",
80
- # viewbox: "0 0 10 6"
81
- # ) do |s|
82
- # s.path(
83
- # stroke: "currentColor",
84
- # stroke_linecap: "round",
85
- # stroke_linejoin: "round",
86
- # stroke_width: "2",
87
- # d: "m1 1 4 4 4-4"
88
- # )
89
- # end
90
- # end
91
- # end
92
20
  end
93
21
  end
94
22
 
@@ -114,7 +42,7 @@ module Plutonium
114
42
 
115
43
  def current_scope
116
44
  # Use the effective scope (includes default when no selection)
117
- current_query_object.selected_scope
45
+ current_query_object.selected_scope&.to_s
118
46
  end
119
47
 
120
48
  def all_scope_active?
@@ -12,7 +12,7 @@ module Plutonium
12
12
 
13
13
  def data_cell(wrapped_object)
14
14
  allowed_actions = compute_allowed_actions(wrapped_object.unwrapped)
15
- SelectionDataCell.new(wrapped_object.field(key).dom.value, allowed_actions)
15
+ SelectionDataCell.new(wrapped_object.field(value_key).dom.value, allowed_actions)
16
16
  end
17
17
 
18
18
  # Add hidden class and Stimulus target to header cell
@@ -34,6 +34,10 @@ module Plutonium
34
34
 
35
35
  private
36
36
 
37
+ def value_key
38
+ options[:value_key] || sample.class.primary_key.to_sym
39
+ end
40
+
37
41
  def bulk_actions
38
42
  options[:bulk_actions] || []
39
43
  end
@@ -43,7 +47,7 @@ module Plutonium
43
47
  end
44
48
 
45
49
  def compute_allowed_actions(record)
46
- return bulk_action_names unless policy_resolver
50
+ return bulk_actions.map { |a| a.name.to_s } unless policy_resolver
47
51
 
48
52
  policy = policy_resolver.call(record)
49
53
  bulk_actions.select { |action|
@@ -49,8 +49,9 @@ module Plutonium
49
49
 
50
50
  render Plutonium::UI::Table::Base.new(collection) do |table|
51
51
  # Selection column for bulk actions (hidden by default, Stimulus shows it)
52
- # Pass bulk actions and policy resolver for per-record authorization
53
- table.selection_column :id,
52
+ # Use :_selection as column key to avoid conflicts with field columns
53
+ # value_key defaults to model's primary_key
54
+ table.selection_column :_selection,
54
55
  bulk_actions:,
55
56
  policy_resolver: ->(record) { policy_for(record:) }
56
57
 
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- VERSION = "0.42.0"
2
+ VERSION = "0.43.1"
3
3
  NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
4
  v[1] = v[1].to_i + 1
5
5
  v[2] = 0