plutonium 0.63.0 → 0.65.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/.claude/skills/plutonium/SKILL.md +4 -1
- data/.claude/skills/plutonium-app/SKILL.md +14 -0
- data/.claude/skills/plutonium-dashboard/SKILL.md +161 -0
- data/.claude/skills/plutonium-resource/SKILL.md +94 -14
- data/.claude/skills/plutonium-ui/SKILL.md +22 -1
- data/.standard.yml +1 -1
- data/CHANGELOG.md +65 -0
- data/CLAUDE.md +282 -210
- data/SECURITY.md +1 -1
- data/app/assets/plutonium-charts.js +20604 -0
- data/app/assets/plutonium-charts.js.map +7 -0
- data/app/assets/plutonium-charts.min.js +41 -0
- data/app/assets/plutonium-charts.min.js.map +7 -0
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +794 -629
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +48 -48
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/plutonium/_flash_alerts.html.erb +2 -2
- data/app/views/plutonium/_resource_header.html.erb +2 -2
- data/app/views/plutonium/_resource_sidebar.html.erb +11 -2
- data/app/views/plutonium/_toast.html.erb +2 -2
- data/app/views/rodauth/_login_form.html.erb +1 -1
- data/app/views/rodauth/_password_visibility.html.erb +1 -1
- data/app/views/rodauth/add_recovery_codes.html.erb +2 -2
- data/app/views/rodauth/change_login.html.erb +2 -2
- data/app/views/rodauth/create_account.html.erb +4 -4
- data/app/views/rodauth/reset_password_request.html.erb +1 -1
- data/app/views/rodauth/verify_account_resend.html.erb +1 -1
- data/app/views/rodauth/webauthn_remove.html.erb +1 -1
- data/config/brakeman.ignore +4 -4
- data/config/initializers/rabl.rb +0 -40
- data/config/locales/en/api_client.yml +22 -0
- data/config/locales/en/async.yml +34 -0
- data/config/locales/en/dashboard.yml +25 -0
- data/config/locales/en/invites.yml +37 -0
- data/config/locales/en/js.yml +40 -0
- data/config/locales/en/kanban.yml +28 -0
- data/config/locales/en/plutonium.yml +26 -0
- data/config/locales/en/positioning.yml +14 -0
- data/config/locales/en/profile.yml +29 -0
- data/config/locales/en/resource.yml +60 -0
- data/config/locales/en/ui.yml +93 -0
- data/config/locales/en/wizard.yml +46 -0
- data/docs/.vitepress/config.ts +34 -1
- data/docs/.vitepress/theme/components/HomeHero.vue +1 -1
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +14 -0
- data/docs/.vitepress/theme/custom.css +34 -377
- data/docs/blog/introducing-plutonium-dashboards.md +175 -0
- data/docs/blog/introducing-plutonium-i18n.md +91 -0
- data/docs/blog/introducing-plutonium.md +4 -5
- data/docs/blog/whats-new-async-kanban-wizards.md +1 -1
- data/docs/guides/dashboards.md +300 -0
- data/docs/guides/index.md +1 -0
- data/docs/guides/kanban.md +2 -2
- data/docs/public/images/blog/dashboards-card-error.png +0 -0
- data/docs/public/images/blog/dashboards-loading.png +0 -0
- data/docs/public/images/blog/dashboards-overview.png +0 -0
- data/docs/public/images/blog/dashboards-sidebar.png +0 -0
- data/docs/public/images/blog/i18n-es-admin.png +0 -0
- data/docs/public/images/guides/dashboard-card-error.png +0 -0
- data/docs/public/images/guides/dashboard-loading.png +0 -0
- data/docs/public/images/guides/dashboard-overview.png +0 -0
- data/docs/public/images/guides/dashboard-sidebar.png +0 -0
- data/docs/reference/app/generators.md +2 -2
- data/docs/reference/app/portals.md +2 -0
- data/docs/reference/configuration.md +84 -27
- data/docs/reference/dashboard/dsl.md +123 -0
- data/docs/reference/dashboard/index.md +35 -0
- data/docs/reference/dashboard/registration.md +76 -0
- data/docs/reference/i18n.md +239 -0
- data/docs/reference/index.md +10 -1
- data/docs/reference/kanban/dsl.md +1 -1
- data/docs/reference/kanban/positioning.md +2 -2
- data/docs/reference/resource/actions.md +1 -1
- data/docs/reference/resource/definition.md +96 -18
- data/docs/reference/resource/export.md +5 -1
- data/docs/reference/resource/index.md +2 -0
- data/docs/reference/{positioning.md → resource/positioning.md} +2 -1
- data/docs/reference/resource/query.md +2 -0
- data/docs/reference/ui/assets.md +8 -0
- data/docs/reference/ui/displays.md +5 -8
- data/docs/reference/ui/index.md +1 -1
- data/docs/reference/wizard/dsl.md +2 -2
- data/esbuild.config.js +5 -2
- data/gemfiles/rails_7.gemfile.lock +3 -3
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +2 -2
- data/lib/active_model/validations/array_validator.rb +1 -1
- data/lib/active_model/validations/attached_validator.rb +1 -1
- data/lib/active_model/validations/url_validator.rb +2 -2
- data/lib/generators/pu/core/assets/assets_generator.rb +36 -21
- data/lib/generators/pu/core/assets/templates/postcss.config.js +1 -1
- data/lib/generators/pu/core/update/update_generator.rb +3 -2
- data/lib/generators/pu/dashboard/dashboard_generator.rb +96 -0
- data/lib/generators/pu/dashboard/templates/dashboard.rb.tt +25 -0
- data/lib/generators/pu/docker/install/install_generator.rb +58 -0
- data/lib/generators/pu/docker/install/templates/Dockerfile.dev.tt +18 -3
- data/lib/generators/pu/docker/install/templates/Dockerfile.tt +20 -5
- data/lib/generators/pu/docker/install/templates/{docker-compose.yml → docker-compose.yml.tt} +1 -1
- data/lib/generators/pu/gem/dotenv/dotenv_generator.rb +1 -1
- data/lib/generators/pu/invites/invitable_generator.rb +18 -1
- data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +0 -95
- data/lib/generators/pu/lib/plutonium_generators/concerns/js_package_manager.rb +146 -0
- data/lib/generators/pu/lib/plutonium_generators/generator.rb +1 -17
- data/lib/generators/pu/pkg/package/package_generator.rb +1 -0
- data/lib/generators/pu/pkg/package/templates/config/locales/en.yml.tt +28 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +1 -0
- data/lib/generators/pu/pkg/portal/templates/config/locales/en.yml.tt +30 -0
- data/lib/generators/pu/res/scaffold/scaffold_generator.rb +0 -8
- data/lib/generators/pu/rodauth/concerns/feature_selector.rb +0 -18
- data/lib/plutonium/action/base.rb +27 -3
- data/lib/plutonium/action/interactive.rb +17 -4
- data/lib/plutonium/api_client/concerns/create_api_client.rb +12 -11
- data/lib/plutonium/api_client/concerns/disable_api_client.rb +3 -3
- data/lib/plutonium/auth/sequel_adapter.rb +1 -1
- data/lib/plutonium/configuration.rb +5 -0
- data/lib/plutonium/core/controller.rb +25 -1
- data/lib/plutonium/core/controllers/authorizable.rb +1 -1
- data/lib/plutonium/dashboard/base.rb +114 -0
- data/lib/plutonium/dashboard/card.rb +200 -0
- data/lib/plutonium/dashboard/controller.rb +101 -0
- data/lib/plutonium/dashboard/dsl.rb +104 -0
- data/lib/plutonium/dashboard/register.rb +40 -0
- data/lib/plutonium/dashboard/route_resolution.rb +30 -0
- data/lib/plutonium/dashboard.rb +42 -0
- data/lib/plutonium/definition/actions.rb +6 -1
- data/lib/plutonium/definition/base.rb +41 -3
- data/lib/plutonium/definition/index_views.rb +1 -1
- data/lib/plutonium/definition/positioning.rb +1 -1
- data/lib/plutonium/definition/presentable.rb +2 -2
- data/lib/plutonium/helpers/assets_helper.rb +15 -2
- data/lib/plutonium/helpers/display_helper.rb +10 -2
- data/lib/plutonium/interaction/async/context.rb +6 -8
- data/lib/plutonium/interaction/async/executor.rb +6 -7
- data/lib/plutonium/interaction/async/run.rb +2 -2
- data/lib/plutonium/interaction/async/run_definition.rb +1 -1
- data/lib/plutonium/interaction/base.rb +2 -0
- data/lib/plutonium/interaction/concerns/dispatchable.rb +15 -3
- data/lib/plutonium/invites/concerns/cancel_invite.rb +3 -3
- data/lib/plutonium/invites/concerns/invite_token.rb +25 -7
- data/lib/plutonium/invites/concerns/invite_user.rb +10 -5
- data/lib/plutonium/invites/concerns/resend_invite.rb +4 -4
- data/lib/plutonium/invites/controller.rb +11 -11
- data/lib/plutonium/kanban/column.rb +13 -3
- data/lib/plutonium/kanban/dsl.rb +7 -4
- data/lib/plutonium/models/has_cents.rb +1 -1
- data/lib/plutonium/profile/security_section.rb +10 -18
- data/lib/plutonium/query/filter.rb +10 -1
- data/lib/plutonium/query/filters/association.rb +27 -2
- data/lib/plutonium/query/filters/boolean.rb +4 -4
- data/lib/plutonium/query/filters/date.rb +4 -17
- data/lib/plutonium/query/filters/date_range.rb +2 -2
- data/lib/plutonium/query/filters/select.rb +10 -1
- data/lib/plutonium/query/filters/text.rb +9 -12
- data/lib/plutonium/railtie.rb +17 -0
- data/lib/plutonium/resource/controller.rb +10 -5
- data/lib/plutonium/resource/controllers/crud_actions.rb +17 -20
- data/lib/plutonium/resource/controllers/export_csv.rb +19 -9
- data/lib/plutonium/resource/controllers/kanban_actions.rb +16 -16
- data/lib/plutonium/resource/controllers/position_actions.rb +5 -5
- data/lib/plutonium/resource/controllers/typeahead.rb +24 -1
- data/lib/plutonium/resource/query_object.rb +30 -3
- data/lib/plutonium/routing/dashboard_registration.rb +137 -0
- data/lib/plutonium/routing/resource_registration.rb +5 -0
- data/lib/plutonium/routing/route_set_extensions.rb +2 -1
- data/lib/plutonium/translation.rb +227 -0
- data/lib/plutonium/ui/actions_dropdown.rb +2 -2
- data/lib/plutonium/ui/breadcrumbs.rb +3 -3
- data/lib/plutonium/ui/color_mode_selector.rb +1 -1
- data/lib/plutonium/ui/component/behaviour.rb +46 -0
- data/lib/plutonium/ui/component/methods.rb +4 -0
- data/lib/plutonium/ui/dashboard/board.rb +80 -0
- data/lib/plutonium/ui/dashboard/card.rb +127 -0
- data/lib/plutonium/ui/dashboard/chart.rb +43 -0
- data/lib/plutonium/ui/dashboard/custom.rb +32 -0
- data/lib/plutonium/ui/dashboard/frame.rb +25 -0
- data/lib/plutonium/ui/dashboard/metric.rb +137 -0
- data/lib/plutonium/ui/dashboard/skeleton.rb +40 -0
- data/lib/plutonium/ui/display/components/attachment.rb +1 -1
- data/lib/plutonium/ui/display/components/badge.rb +7 -1
- data/lib/plutonium/ui/display/components/boolean.rb +2 -2
- data/lib/plutonium/ui/display/resource.rb +23 -16
- data/lib/plutonium/ui/export_button.rb +3 -3
- data/lib/plutonium/ui/form/components/key_value_store.rb +5 -5
- data/lib/plutonium/ui/form/components/password.rb +2 -2
- data/lib/plutonium/ui/form/components/resource_select.rb +27 -0
- data/lib/plutonium/ui/form/components/secure_association.rb +2 -0
- data/lib/plutonium/ui/form/components/uppy.rb +11 -3
- data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +1 -1
- data/lib/plutonium/ui/form/concerns/renders_repeater_row_controls.rb +5 -4
- data/lib/plutonium/ui/form/concerns/renders_structured_inputs.rb +1 -1
- data/lib/plutonium/ui/form/query.rb +8 -8
- data/lib/plutonium/ui/form/resource.rb +54 -26
- data/lib/plutonium/ui/frame_navigator_panel.rb +4 -4
- data/lib/plutonium/ui/grid/card.rb +3 -3
- data/lib/plutonium/ui/grid/resource.rb +3 -3
- data/lib/plutonium/ui/interaction/async/run_progress.rb +6 -6
- data/lib/plutonium/ui/interaction/async/running_banner.rb +2 -2
- data/lib/plutonium/ui/kanban/column.rb +6 -6
- data/lib/plutonium/ui/layout/base.rb +19 -1
- data/lib/plutonium/ui/layout/header.rb +1 -1
- data/lib/plutonium/ui/layout/icon_rail.rb +4 -4
- data/lib/plutonium/ui/layout/rodauth_layout.rb +1 -1
- data/lib/plutonium/ui/layout/sidebar.rb +1 -1
- data/lib/plutonium/ui/layout/topbar.rb +2 -2
- data/lib/plutonium/ui/modal/base.rb +4 -4
- data/lib/plutonium/ui/nav_grid_menu.rb +1 -1
- data/lib/plutonium/ui/nav_user.rb +2 -2
- data/lib/plutonium/ui/page/base.rb +10 -2
- data/lib/plutonium/ui/page/dashboard.rb +54 -0
- data/lib/plutonium/ui/page/edit.rb +3 -3
- data/lib/plutonium/ui/page/interactive_action.rb +2 -2
- data/lib/plutonium/ui/page/new.rb +3 -3
- data/lib/plutonium/ui/page/show.rb +2 -2
- data/lib/plutonium/ui/page/wizard.rb +22 -16
- data/lib/plutonium/ui/page/wizard_chooser.rb +10 -10
- data/lib/plutonium/ui/page/wizard_completed.rb +2 -2
- data/lib/plutonium/ui/skeleton_table.rb +1 -1
- data/lib/plutonium/ui/table/base.rb +2 -2
- data/lib/plutonium/ui/table/components/attachment.rb +1 -1
- data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +19 -3
- data/lib/plutonium/ui/table/components/drag_handle.rb +3 -3
- data/lib/plutonium/ui/table/components/filter_form.rb +11 -10
- data/lib/plutonium/ui/table/components/filter_pills.rb +4 -4
- data/lib/plutonium/ui/table/components/pagy_info.rb +61 -17
- data/lib/plutonium/ui/table/components/pagy_pagination.rb +9 -5
- data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +1 -1
- data/lib/plutonium/ui/table/components/scopes_bar.rb +6 -2
- data/lib/plutonium/ui/table/components/scopes_pills.rb +3 -3
- data/lib/plutonium/ui/table/components/selection_column.rb +1 -1
- data/lib/plutonium/ui/table/components/toolbar.rb +2 -2
- data/lib/plutonium/ui/table/components/view_switcher.rb +12 -8
- data/lib/plutonium/ui/table/resource.rb +53 -17
- data/lib/plutonium/ui/wizard/review.rb +7 -7
- data/lib/plutonium/ui/wizard/stepper.rb +1 -1
- data/lib/plutonium/ui/wizard/summary_display.rb +6 -0
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium/wizard/base.rb +2 -0
- data/lib/plutonium/wizard/driving.rb +8 -2
- data/lib/plutonium/wizard/dsl.rb +4 -2
- data/lib/plutonium/wizard/field_importer.rb +1 -8
- data/lib/plutonium/wizard/review_step.rb +8 -1
- data/lib/plutonium/wizard/step.rb +14 -3
- data/package.json +4 -2
- data/plutonium.gemspec +15 -30
- data/postcss-gem-import.cjs +34 -0
- data/postcss-gem-import.js +3 -28
- data/src/css/components.css +91 -0
- data/src/css/slim_select.css +0 -38
- data/src/css/tokens.css +24 -0
- data/src/js/controllers/attachment_input_controller.js +15 -6
- data/src/js/controllers/chart_controller.js +162 -0
- data/src/js/controllers/clipboard_controller.js +3 -2
- data/src/js/controllers/dirty_form_guard_controller.js +4 -3
- data/src/js/controllers/easymde_controller.js +3 -0
- data/src/js/controllers/filter_panel_controller.js +15 -6
- data/src/js/controllers/flatpickr_controller.js +8 -0
- data/src/js/controllers/frame_refresh_controller.js +59 -0
- data/src/js/controllers/intl_tel_input_controller.js +5 -0
- data/src/js/controllers/register_controllers.js +4 -0
- data/src/js/controllers/slim_select_controller.js +79 -176
- data/src/js/core.js +9 -1
- data/src/js/i18n.js +69 -0
- data/src/js/plutonium-charts.js +15 -0
- data/src/js/turbo/turbo_confirm.js +5 -3
- metadata +71 -31
- data/.yarnrc.yml +0 -8
- data/config/initializers/hotwire_turbo_monkey_patches.rb +0 -12
- data/lib/generators/pu/field/input/input_generator.rb +0 -32
- data/lib/generators/pu/field/input/templates/input.rb.tt +0 -15
- data/lib/generators/pu/lib/plutonium_generators/concerns/config.rb +0 -39
- data/lib/generators/pu/lib/plutonium_generators/concerns/serializer.rb +0 -39
- data/lib/generators/pu/lib/plutonium_generators/installer.rb +0 -205
- data/lib/generators/pu/res/scaffold/templates/presenter.rb.tt +0 -13
- data/lib/generators/pu/res/scaffold/templates/query_object.rb.tt +0 -17
- data/public/plutonium-assets/application.js +0 -31419
- data/public/plutonium-assets/plutonium.ico +0 -0
- data/yarn.lock +0 -6858
|
@@ -12,10 +12,12 @@ module Plutonium
|
|
|
12
12
|
# through. The Stimulus controller sets the cookie on click,
|
|
13
13
|
# then reloads.
|
|
14
14
|
class ViewSwitcher < Plutonium::UI::Component::Base
|
|
15
|
+
# Labels are translation keys, resolved at render time so the
|
|
16
|
+
# switcher follows the request's locale.
|
|
15
17
|
SEGMENT_LABELS = {
|
|
16
|
-
table: {label: "
|
|
17
|
-
grid: {label: "
|
|
18
|
-
kanban: {label: "
|
|
18
|
+
table: {label: "plutonium.ui.table.views.table", icon: Phlex::TablerIcons::Table},
|
|
19
|
+
grid: {label: "plutonium.ui.table.views.grid", icon: Phlex::TablerIcons::LayoutGrid},
|
|
20
|
+
kanban: {label: "plutonium.ui.table.views.kanban", icon: Phlex::TablerIcons::LayoutKanban}
|
|
19
21
|
}.freeze
|
|
20
22
|
|
|
21
23
|
def initialize(views:, current:, cookie_name:, cookie_path: "/")
|
|
@@ -32,7 +34,7 @@ module Plutonium
|
|
|
32
34
|
def view_template
|
|
33
35
|
div(
|
|
34
36
|
role: "tablist",
|
|
35
|
-
aria: {label: "
|
|
37
|
+
aria: {label: t("plutonium.ui.table.view")},
|
|
36
38
|
class: "inline-flex h-8 rounded-md border border-[var(--pu-border)] bg-[var(--pu-surface)] overflow-hidden",
|
|
37
39
|
data: {
|
|
38
40
|
controller: "view-switcher",
|
|
@@ -49,7 +51,9 @@ module Plutonium
|
|
|
49
51
|
private
|
|
50
52
|
|
|
51
53
|
def render_segment(key, last:)
|
|
52
|
-
meta = SEGMENT_LABELS
|
|
54
|
+
meta = SEGMENT_LABELS[key]
|
|
55
|
+
label = meta ? t(meta[:label]) : key.to_s.titleize
|
|
56
|
+
icon = meta ? meta[:icon] : Phlex::TablerIcons::LayoutGrid
|
|
53
57
|
active = key == @current
|
|
54
58
|
|
|
55
59
|
classes = ["px-2.5 inline-flex items-center gap-1.5 text-sm transition-colors"]
|
|
@@ -64,15 +68,15 @@ module Plutonium
|
|
|
64
68
|
type: "button",
|
|
65
69
|
role: "tab",
|
|
66
70
|
class: classes.join(" "),
|
|
67
|
-
title:
|
|
71
|
+
title: label,
|
|
68
72
|
aria: {selected: active.to_s},
|
|
69
73
|
data: {
|
|
70
74
|
action: "click->view-switcher#select",
|
|
71
75
|
view_switcher_view_param: key.to_s
|
|
72
76
|
}
|
|
73
77
|
) do
|
|
74
|
-
render
|
|
75
|
-
span {
|
|
78
|
+
render icon.new(class: "w-4 h-4 shrink-0")
|
|
79
|
+
span { label }
|
|
76
80
|
end
|
|
77
81
|
end
|
|
78
82
|
end
|
|
@@ -84,7 +84,7 @@ module Plutonium
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def render_empty_card
|
|
87
|
-
EmptyCard("
|
|
87
|
+
EmptyCard(t("plutonium.ui.empty_state.none_available", resources: resource_name_plural(resource_class).downcase)) {
|
|
88
88
|
action = resource_definition.defined_actions[:new]
|
|
89
89
|
if action&.permitted_by?(current_policy)
|
|
90
90
|
url = route_options_to_url(action.route_options, resource_class)
|
|
@@ -121,19 +121,33 @@ module Plutonium
|
|
|
121
121
|
column_definition = resource_definition.defined_columns[name] || {}
|
|
122
122
|
column_options = column_definition[:options] || {}
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
field_options = resolve_field_level_procs(field_options)
|
|
125
|
+
display_options = resolve_field_level_procs(display_options)
|
|
126
|
+
column_options = resolve_field_level_procs(column_options)
|
|
127
|
+
|
|
128
|
+
# A `column` condition or a surface-neutral `field` condition hides
|
|
129
|
+
# the column. A `display` condition stays show-only: it may reference
|
|
130
|
+
# `object`, and the table has no single record to evaluate it against.
|
|
131
|
+
condition = column_options[:condition] || field_options[:condition]
|
|
126
132
|
conditionally_hidden = condition && !instance_exec(&condition)
|
|
127
133
|
next if conditionally_hidden
|
|
128
134
|
|
|
129
|
-
|
|
135
|
+
# A column that says anything about RENDERING (`as:`, a component
|
|
136
|
+
# attribute, or a block) renders alone: its own `as:` and attributes,
|
|
137
|
+
# no display inheritance. A column that only touches the header
|
|
138
|
+
# (align/label/condition) layers on whatever the display renders, so
|
|
139
|
+
# `display :x, as: :badge` + `column :x, align: :end` keeps the badge.
|
|
140
|
+
# Only attributes are inherited, never the display's block.
|
|
141
|
+
column_render_options = column_options.except(:wrapper, :condition, *COLUMN_FIELD_LEVEL_KEYS)
|
|
142
|
+
renders_alone = column_definition[:block] || column_render_options.present?
|
|
143
|
+
source_options = renders_alone ? column_options : display_options
|
|
144
|
+
|
|
145
|
+
tag = source_options[:as] || field_options[:as]
|
|
130
146
|
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
column_tag_attributes = column_options.except(:wrapper, :as, :align, :condition, *field_level_keys)
|
|
136
|
-
tag_attributes = display_tag_attributes.merge(column_tag_attributes)
|
|
147
|
+
# Strip the field-level help keys and the header keys: they configure
|
|
148
|
+
# the field or the column, not the tag, and would otherwise leak as an
|
|
149
|
+
# HTML attribute.
|
|
150
|
+
tag_attributes = source_options.except(:wrapper, :as, :condition, *FIELD_LEVEL_KEYS, *COLUMN_FIELD_LEVEL_KEYS)
|
|
137
151
|
|
|
138
152
|
# A `formatter:` produces the cell string itself, so render it
|
|
139
153
|
# through the formatted-value component regardless of the column's
|
|
@@ -142,9 +156,17 @@ module Plutonium
|
|
|
142
156
|
tag = :formatted_value if tag_attributes.key?(:formatter)
|
|
143
157
|
|
|
144
158
|
tag_block = if column_definition[:block]
|
|
145
|
-
# User
|
|
159
|
+
# User blocks receive the raw record and are instance_exec'd against
|
|
160
|
+
# this table page, exactly as display blocks are against the show
|
|
161
|
+
# page: `self` has resource_definition/current_user/helpers and the
|
|
162
|
+
# tag methods, so the body can emit markup directly. The cell is a
|
|
163
|
+
# zero-arity proc; Phlex `render`s it in the <td>, sharing this
|
|
164
|
+
# component's buffer.
|
|
146
165
|
user_block = column_definition[:block]
|
|
147
|
-
->(wrapped_object, _key) {
|
|
166
|
+
->(wrapped_object, _key) {
|
|
167
|
+
record = wrapped_object.unwrapped
|
|
168
|
+
-> { render_column_block_result(instance_exec(record, &user_block)) }
|
|
169
|
+
}
|
|
148
170
|
else
|
|
149
171
|
->(wrapped_object, key) {
|
|
150
172
|
f = wrapped_object.field(key)
|
|
@@ -160,9 +182,11 @@ module Plutonium
|
|
|
160
182
|
tag_block = wrap_with_drag_handle(tag_block)
|
|
161
183
|
end
|
|
162
184
|
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
|
|
185
|
+
# Header options (label/align) come from field, then display, then
|
|
186
|
+
# column, the most specific winning. Field-level help keys like
|
|
187
|
+
# description and placeholder have no place in a table cell.
|
|
188
|
+
header_options = display_options.slice(*COLUMN_FIELD_LEVEL_KEYS).merge(column_options.slice(*COLUMN_FIELD_LEVEL_KEYS))
|
|
189
|
+
field_options = field_options.except(:condition).merge(header_options)
|
|
166
190
|
table.column name,
|
|
167
191
|
**field_options,
|
|
168
192
|
sort_params: current_query_object.sort_params_for(name),
|
|
@@ -209,6 +233,18 @@ module Plutonium
|
|
|
209
233
|
#
|
|
210
234
|
# The per-record `reposition?` check, by contrast, HAS to run per row.
|
|
211
235
|
# Rows that fail it render exactly as they did before, unwrapped.
|
|
236
|
+
# A block that emitted markup returns nil (a no-op render); a String,
|
|
237
|
+
# component or proc renders as Phlex normally would; anything else
|
|
238
|
+
# (a count, a date…) reads as text instead of raising.
|
|
239
|
+
def render_column_block_result(result)
|
|
240
|
+
case result
|
|
241
|
+
when nil, String, Phlex::SGML, Proc, Enumerable
|
|
242
|
+
render result
|
|
243
|
+
else
|
|
244
|
+
plain result.to_s
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
212
248
|
def wrap_with_drag_handle(inner)
|
|
213
249
|
sort_url = position_grip_sort_url
|
|
214
250
|
|
|
@@ -261,7 +297,7 @@ module Plutonium
|
|
|
261
297
|
"data-[open]:translate-x-0 " \
|
|
262
298
|
"flex flex-col",
|
|
263
299
|
role: "dialog",
|
|
264
|
-
aria: {label: "
|
|
300
|
+
aria: {label: t("plutonium.ui.table.filters"), hidden: "true", modal: "true"},
|
|
265
301
|
data: {filter_panel_target: "panel"}
|
|
266
302
|
) do
|
|
267
303
|
render Plutonium::UI::Table::Components::FilterForm.new(
|
|
@@ -279,7 +315,7 @@ module Plutonium
|
|
|
279
315
|
|
|
280
316
|
def render_footer
|
|
281
317
|
div(class: "lg:sticky bottom-[-2px] mt-1 p-4 pb-6 w-full z-30 bg-[var(--pu-body)]") {
|
|
282
|
-
TableInfo(pagy_instance)
|
|
318
|
+
TableInfo(pagy_instance, item_name: resource_name(resource_class, pagy_instance.count))
|
|
283
319
|
TablePagination(pagy_instance)
|
|
284
320
|
}
|
|
285
321
|
end
|
|
@@ -69,7 +69,7 @@ module Plutonium
|
|
|
69
69
|
|
|
70
70
|
div(class: "pu-wizard-review-outstanding rounded-lg border border-warning-300 bg-warning-50 dark:border-warning-800 dark:bg-warning-950/30 p-4 mb-6", role: "alert") do
|
|
71
71
|
p(class: "text-sm font-medium text-[var(--pu-text)] mb-2") do
|
|
72
|
-
"
|
|
72
|
+
t("plutonium.wizard.review.outstanding")
|
|
73
73
|
end
|
|
74
74
|
ul(class: "space-y-1") do
|
|
75
75
|
incomplete.each do |step|
|
|
@@ -78,7 +78,7 @@ module Plutonium
|
|
|
78
78
|
href: @step_url.call(step.key),
|
|
79
79
|
class: "text-sm font-medium text-primary-600 dark:text-primary-400 hover:underline",
|
|
80
80
|
data: {wizard_review_fix: step.key}
|
|
81
|
-
) { "
|
|
81
|
+
) { t("plutonium.wizard.review.fix_step", step: step.label) }
|
|
82
82
|
end
|
|
83
83
|
end
|
|
84
84
|
end
|
|
@@ -109,7 +109,7 @@ module Plutonium
|
|
|
109
109
|
a(
|
|
110
110
|
href: @step_url.call(step.key),
|
|
111
111
|
class: "shrink-0 text-xs font-medium text-primary-600 dark:text-primary-400 hover:underline"
|
|
112
|
-
) { "
|
|
112
|
+
) { t("plutonium.wizard.review.edit") }
|
|
113
113
|
end
|
|
114
114
|
div(class: "px-5 py-4") do
|
|
115
115
|
# Decorate so attachment fields resolve to displayable attachments —
|
|
@@ -147,14 +147,14 @@ module Plutonium
|
|
|
147
147
|
name.to_s.humanize
|
|
148
148
|
end
|
|
149
149
|
if rows.empty?
|
|
150
|
-
p(class: "text-sm text-[var(--pu-text-subtle)]") { "
|
|
150
|
+
p(class: "text-sm text-[var(--pu-text-subtle)]") { t("plutonium.wizard.review.none") }
|
|
151
151
|
else
|
|
152
152
|
ul(class: "space-y-1.5") do
|
|
153
153
|
rows.each do |row|
|
|
154
154
|
li(class: "flex flex-wrap items-center gap-x-2 gap-y-0.5 rounded-[var(--pu-radius-md)] bg-[var(--pu-surface-alt)] px-3 py-2 text-sm text-[var(--pu-text)]") do
|
|
155
155
|
row.each do |key, value|
|
|
156
156
|
span do
|
|
157
|
-
span(class: "text-[var(--pu-text-muted)]") { "
|
|
157
|
+
span(class: "text-[var(--pu-text-muted)]") { t("plutonium.wizard.review.row_label", label: key.to_s.humanize) }
|
|
158
158
|
plain value.to_s
|
|
159
159
|
end
|
|
160
160
|
end
|
|
@@ -186,9 +186,9 @@ module Plutonium
|
|
|
186
186
|
div(class: "mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-success-100 text-success-600 dark:bg-success-900/30 dark:text-success-400") do
|
|
187
187
|
render Phlex::TablerIcons::Check.new(class: "h-7 w-7")
|
|
188
188
|
end
|
|
189
|
-
h3(class: "text-lg font-semibold tracking-tight text-[var(--pu-text)]") { "
|
|
189
|
+
h3(class: "text-lg font-semibold tracking-tight text-[var(--pu-text)]") { t("plutonium.wizard.review.ready_title") }
|
|
190
190
|
p(class: "mt-1.5 max-w-prose text-sm text-[var(--pu-text-muted)]") do
|
|
191
|
-
"
|
|
191
|
+
t("plutonium.wizard.review.ready_message")
|
|
192
192
|
end
|
|
193
193
|
end
|
|
194
194
|
end
|
|
@@ -31,7 +31,7 @@ module Plutonium
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def view_template
|
|
34
|
-
nav(class: "pu-wizard-stepper mb-7", aria_label: "
|
|
34
|
+
nav(class: "pu-wizard-stepper mb-7", aria_label: t("plutonium.wizard.stepper.progress")) do
|
|
35
35
|
ol(class: "pu-wizard-steps") do
|
|
36
36
|
@steps.each_with_index do |step, index|
|
|
37
37
|
render_step(step, index)
|
|
@@ -38,6 +38,12 @@ module Plutonium
|
|
|
38
38
|
|
|
39
39
|
def render_summary_field(name)
|
|
40
40
|
input_options = @summary_inputs[name]&.dig(:options) || {}
|
|
41
|
+
# Summarise only what the step showed: an input hidden by its own
|
|
42
|
+
# `condition:` was never answered, so it gets no (empty) row. The
|
|
43
|
+
# condition runs against this display exactly as it ran against the
|
|
44
|
+
# step form — `object` is the same staged data either way.
|
|
45
|
+
condition = input_options[:condition]
|
|
46
|
+
return if condition && !instance_exec(&condition)
|
|
41
47
|
field_options = input_options[:label] ? {label: input_options[:label]} : {}
|
|
42
48
|
|
|
43
49
|
# A currency input stages a plain decimal; the data snapshot carries no
|
data/lib/plutonium/version.rb
CHANGED
|
@@ -33,6 +33,8 @@ module Plutonium
|
|
|
33
33
|
# other.
|
|
34
34
|
WIZARD_RETURN_TO_KEY = "plutonium_wizard_return_to"
|
|
35
35
|
|
|
36
|
+
AuthorizationDeniedResult = Struct.new(:message)
|
|
37
|
+
|
|
36
38
|
# The per-wizard key under {SESSION_TOKENS_KEY} for a guest run's token.
|
|
37
39
|
def self.session_token_key(wizard_class)
|
|
38
40
|
wizard_class.name.underscore.tr("/", "_")
|
|
@@ -516,7 +518,7 @@ module Plutonium
|
|
|
516
518
|
def deny_wizard_resume_for_other_user!(runner)
|
|
517
519
|
return unless runner.forbidden?
|
|
518
520
|
|
|
519
|
-
raise ActiveRecord::RecordNotFound, "wizard
|
|
521
|
+
raise ActiveRecord::RecordNotFound, I18n.t("plutonium.wizard.run_not_found")
|
|
520
522
|
end
|
|
521
523
|
|
|
522
524
|
# Entry auth (§5.2 / §6.5). A wizard may define `authorize?` (default allow);
|
|
@@ -528,7 +530,11 @@ module Plutonium
|
|
|
528
530
|
wizard = runner.wizard
|
|
529
531
|
return if wizard.authorize?
|
|
530
532
|
|
|
531
|
-
raise ActionPolicy::Unauthorized.new(
|
|
533
|
+
raise ::ActionPolicy::Unauthorized.new(
|
|
534
|
+
wizard,
|
|
535
|
+
:authorize?,
|
|
536
|
+
AuthorizationDeniedResult.new(I18n.t("plutonium.wizard.authorization_denied"))
|
|
537
|
+
)
|
|
532
538
|
end
|
|
533
539
|
|
|
534
540
|
# --- params ---
|
data/lib/plutonium/wizard/dsl.rb
CHANGED
|
@@ -44,7 +44,8 @@ module Plutonium
|
|
|
44
44
|
fields: capture,
|
|
45
45
|
on_submit: capture.delete_hook(:on_submit),
|
|
46
46
|
on_rollback: capture.delete_hook(:on_rollback),
|
|
47
|
-
using_spec: capture.using_spec
|
|
47
|
+
using_spec: capture.using_spec,
|
|
48
|
+
wizard_class: self
|
|
48
49
|
)
|
|
49
50
|
end
|
|
50
51
|
|
|
@@ -60,7 +61,7 @@ module Plutonium
|
|
|
60
61
|
# `header:` (default true) controls the step-header section (the label +
|
|
61
62
|
# the "check everything over" prompt). `header: false` drops it entirely,
|
|
62
63
|
# leaving just the review body in the card — for a chromeless finish.
|
|
63
|
-
def review(label:
|
|
64
|
+
def review(label: nil, description: nil, condition: nil, summary: true, header: true, &block)
|
|
64
65
|
assert_not_after_review!(:review)
|
|
65
66
|
steps << ReviewStep.new(label:, description:, condition:, summary:, header:, block:)
|
|
66
67
|
end
|
|
@@ -344,6 +345,7 @@ module Plutonium
|
|
|
344
345
|
subclass.instance_variable_set(:@anchor_resolver, @anchor_resolver)
|
|
345
346
|
subclass.instance_variable_set(:@navigation, @navigation)
|
|
346
347
|
subclass.instance_variable_set(:@stepper, @stepper)
|
|
348
|
+
subclass.instance_variable_set(:@width, @width)
|
|
347
349
|
subclass.instance_variable_set(:@on_relaunch, @on_relaunch)
|
|
348
350
|
subclass.instance_variable_set(:@cleanup_after, @cleanup_after)
|
|
349
351
|
subclass.instance_variable_set(:@cleanup_after_set, @cleanup_after_set)
|
|
@@ -36,14 +36,7 @@ module Plutonium
|
|
|
36
36
|
FORM_VALIDATOR_KINDS = %i[presence length numericality format inclusion].freeze
|
|
37
37
|
|
|
38
38
|
# The resolved import surface for one `using:` declaration.
|
|
39
|
-
Spec = Struct.new(:attribute_schema, :inputs, :form_layout, :validate_fn, :form_validators)
|
|
40
|
-
# Run the imported validation over a staged data slice, returning a hash of
|
|
41
|
-
# {attribute => [messages]} for the imported fields + :base. Empty when
|
|
42
|
-
# `validate: false`.
|
|
43
|
-
def validate(data_slice)
|
|
44
|
-
validate_fn ? validate_fn.call(data_slice) : {}
|
|
45
|
-
end
|
|
46
|
-
end
|
|
39
|
+
Spec = Struct.new(:attribute_schema, :inputs, :form_layout, :validate_fn, :form_validators)
|
|
47
40
|
|
|
48
41
|
class << self
|
|
49
42
|
# @param using [Class] an ActiveRecord model class
|
|
@@ -27,13 +27,20 @@ module Plutonium
|
|
|
27
27
|
|
|
28
28
|
attr_reader :block, :summary, :header
|
|
29
29
|
|
|
30
|
-
def initialize(key: :review, label:
|
|
30
|
+
def initialize(key: :review, label: nil, description: nil, condition: nil, summary: true, header: true, block: nil)
|
|
31
31
|
super(key:, label:, description:, condition:, fields: EmptyFields.new)
|
|
32
|
+
@explicit_label = label
|
|
32
33
|
@summary = summary
|
|
33
34
|
@header = header
|
|
34
35
|
@block = block
|
|
35
36
|
end
|
|
36
37
|
|
|
38
|
+
# The author's `label:` when given; otherwise the translated default,
|
|
39
|
+
# looked up per call so it follows the request's locale.
|
|
40
|
+
def label
|
|
41
|
+
Plutonium::Translation.resolve(@explicit_label) || I18n.t("plutonium.wizard.review.label")
|
|
42
|
+
end
|
|
43
|
+
|
|
37
44
|
def review? = true
|
|
38
45
|
|
|
39
46
|
# Whether the auto-summary of completed steps renders in the COMPLETE state
|
|
@@ -6,13 +6,14 @@ module Plutonium
|
|
|
6
6
|
# field surface, per-step hooks, and the `using:` import marker (resolved in
|
|
7
7
|
# Task 3). A value object — holds no runtime state.
|
|
8
8
|
class Step
|
|
9
|
-
attr_reader :key, :
|
|
9
|
+
attr_reader :key, :condition, :fields,
|
|
10
10
|
:on_submit, :on_rollback, :using_spec
|
|
11
11
|
|
|
12
12
|
def initialize(key:, fields:, label: nil, description: nil, condition: nil,
|
|
13
|
-
on_submit: nil, on_rollback: nil, using_spec: nil)
|
|
13
|
+
on_submit: nil, on_rollback: nil, using_spec: nil, wizard_class: nil)
|
|
14
14
|
@key = key
|
|
15
|
-
@label = label
|
|
15
|
+
@label = label
|
|
16
|
+
@wizard_class = wizard_class
|
|
16
17
|
@description = description
|
|
17
18
|
@condition = condition
|
|
18
19
|
@fields = fields
|
|
@@ -21,6 +22,16 @@ module Plutonium
|
|
|
21
22
|
@using_spec = using_spec
|
|
22
23
|
end
|
|
23
24
|
|
|
25
|
+
# `label:` if given, else plutonium.wizard_steps.<wizard>.<key>, else the
|
|
26
|
+
# humanized key. Resolved on every read so it follows the request locale.
|
|
27
|
+
def label
|
|
28
|
+
Plutonium::Translation.resolve(@label) ||
|
|
29
|
+
Plutonium::Translation.label_for(:wizard_step, @wizard_class, key) ||
|
|
30
|
+
key.to_s.humanize
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def description = Plutonium::Translation.resolve(@description)
|
|
34
|
+
|
|
24
35
|
def review? = false
|
|
25
36
|
|
|
26
37
|
# The step's form sections (§7.1): inline `form_layout` wins, else the layout
|
data/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@radioactive-labs/plutonium",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.0",
|
|
4
4
|
"description": "Build production-ready Rails apps in minutes, not days. Convention-driven, fully customizable, AI-ready.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/js/core.js",
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
"@uppy/dashboard": "^4.1.3",
|
|
26
26
|
"@uppy/image-editor": "^3.2.1",
|
|
27
27
|
"@uppy/xhr-upload": "^4.2.3",
|
|
28
|
+
"chart.js": "^4.5.1",
|
|
29
|
+
"chartkick": "^5.0.1",
|
|
28
30
|
"dompurify": "^3.4.11",
|
|
29
31
|
"lodash.debounce": "^4.0.8",
|
|
30
32
|
"marked": "^15.0.3"
|
|
@@ -55,7 +57,7 @@
|
|
|
55
57
|
},
|
|
56
58
|
"scripts": {
|
|
57
59
|
"dev": "concurrently \"yarn css:dev\" \"yarn js:dev\"",
|
|
58
|
-
"build": "yarn js:prod && yarn css:prod",
|
|
60
|
+
"build": "rm -rf src/dist && yarn js:prod && yarn css:prod",
|
|
59
61
|
"prepare": "yarn build",
|
|
60
62
|
"css:dev": "chokidar \"src/css/**/*.css\" \"src/js/**/*.js\" \"app/views/**/*.{rb,erb,js}\" \"lib/plutonium/**/*.rb\" -c \"postcss src/css/plutonium.entry.css -o src/build/plutonium.css --dev\" --initial",
|
|
61
63
|
"js:dev": "node esbuild.config.js --dev",
|
data/plutonium.gemspec
CHANGED
|
@@ -12,35 +12,17 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
"definitions, and portals that make building complex apps faster. Built for the AI era with Claude Code skills."
|
|
13
13
|
spec.homepage = "https://radioactive-labs.github.io/plutonium-core/"
|
|
14
14
|
spec.license = "MIT"
|
|
15
|
-
# Ruby 3.2
|
|
16
|
-
# post_install_message
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
spec.required_ruby_version = ">= 3.
|
|
15
|
+
# Ruby 3.2 was dropped here, one release after being deprecated in the
|
|
16
|
+
# post_install_message, so 3.2 users had a release that both carried the
|
|
17
|
+
# fixes and still installed for them. See the pagy dependency below for why
|
|
18
|
+
# 3.2 could not be kept.
|
|
19
|
+
spec.required_ruby_version = ">= 3.3"
|
|
20
20
|
|
|
21
21
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
22
22
|
# Require multi-factor auth for privileged RubyGems actions (push, yank,
|
|
23
23
|
# owner changes). `gem push` will prompt for an OTP at release time.
|
|
24
24
|
spec.metadata["rubygems_mfa_required"] = "true"
|
|
25
25
|
|
|
26
|
-
# Prints on EVERY install: only notices still actionable for someone
|
|
27
|
-
# installing THIS version.
|
|
28
|
-
spec.post_install_message = <<~MSG
|
|
29
|
-
⚠️ Ruby 3.2 support ends in the NEXT release
|
|
30
|
-
|
|
31
|
-
This is the last Plutonium release that installs on Ruby 3.2.
|
|
32
|
-
|
|
33
|
-
Why: pagy patches CVE-2026-54659 (a path-traversal in
|
|
34
|
-
`Pagy::I18n.locale=`) only in >= 43.5.6, and pagy dropped Ruby 3.2 in
|
|
35
|
-
43.5.0. No pagy version is both patched and installable on 3.2, so
|
|
36
|
-
continuing to support it means shipping a known-vulnerable dependency.
|
|
37
|
-
|
|
38
|
-
Ruby 3.2 reached end-of-life in March 2026.
|
|
39
|
-
|
|
40
|
-
Action: upgrade to Ruby 3.3 or newer before your next Plutonium bump.
|
|
41
|
-
If you stay on 3.2, `bundle update plutonium` will hold you here.
|
|
42
|
-
MSG
|
|
43
|
-
|
|
44
26
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
45
27
|
spec.metadata["source_code_uri"] = "https://github.com/radioactive-labs/plutonium-core"
|
|
46
28
|
# spec.metadata["changelog_uri"] = "https://google.com"
|
|
@@ -50,7 +32,10 @@ Gem::Specification.new do |spec|
|
|
|
50
32
|
spec.files = Dir.chdir(__dir__) do
|
|
51
33
|
`git ls-files -z`.split("\x0").reject do |f|
|
|
52
34
|
(File.expand_path(f) == __FILE__) ||
|
|
53
|
-
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
|
35
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile]) ||
|
|
36
|
+
# Maintainer tooling; host apps read tailwind.options.js and the postcss
|
|
37
|
+
# plugin from the gem, not the lockfile or yarn settings.
|
|
38
|
+
%w[.yarnrc.yml yarn.lock public/plutonium-assets/].any? { |prefix| f.start_with?(prefix) }
|
|
54
39
|
end
|
|
55
40
|
end
|
|
56
41
|
spec.bindir = "exe"
|
|
@@ -61,12 +46,12 @@ Gem::Specification.new do |spec|
|
|
|
61
46
|
spec.add_dependency "rails", ">= 7.2.3.1"
|
|
62
47
|
spec.add_dependency "csv" # CSV export; no longer a default gem on Ruby 3.4+
|
|
63
48
|
spec.add_dependency "listen", "~> 3.8"
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
spec.add_dependency "pagy", "~> 43.0"
|
|
49
|
+
# >= 43.5.6 patches CVE-2026-54659 (Pagy::I18n.locale= used its argument as a
|
|
50
|
+
# path component without validating it). Pinned so an app carrying an older
|
|
51
|
+
# pagy in its lockfile cannot stay on a vulnerable 43.x through a plutonium
|
|
52
|
+
# bump. This pin is what requires Ruby >= 3.3 above: pagy dropped 3.2 in
|
|
53
|
+
# 43.5.0, so no version is both patched and installable on 3.2.
|
|
54
|
+
spec.add_dependency "pagy", "~> 43.0", ">= 43.5.6"
|
|
70
55
|
spec.add_dependency "rabl", "~> 0.17.0" # TODO: what to do with RABL
|
|
71
56
|
spec.add_dependency "semantic_range", "~> 3.0"
|
|
72
57
|
spec.add_dependency "tty-prompt", "~> 0.23.1"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// CommonJS on purpose: host apps load this by absolute path from their
|
|
2
|
+
// postcss.config.js. This directory's package.json declares "type": "module",
|
|
3
|
+
// so a .js file here is ESM, which the require()-based loader in
|
|
4
|
+
// postcss-load-config 4 (postcss-cli 10) passes to PostCSS as a module
|
|
5
|
+
// namespace instead of a plugin. A .cjs file loads under require() and
|
|
6
|
+
// import() alike.
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const plugin = (opts = {}) => {
|
|
10
|
+
return {
|
|
11
|
+
postcssPlugin: 'postcss-gem-import',
|
|
12
|
+
Once(root) {
|
|
13
|
+
root.walkAtRules('import', (rule) => {
|
|
14
|
+
const importPath = rule.params.replace(/['"]/g, '');
|
|
15
|
+
|
|
16
|
+
if (importPath.startsWith('gem:')) {
|
|
17
|
+
const gemName = importPath.split('gem:')[1].split('/')[0];
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const gemPath = execSync(`bundle show ${gemName}`, { encoding: 'utf8' }).trim();
|
|
21
|
+
const newPath = importPath.replace(`gem:${gemName}`, gemPath);
|
|
22
|
+
rule.params = `"${newPath}"`;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw rule.error(`Failed to resolve gem path for ${gemName}: ${error.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
plugin.postcss = true;
|
|
33
|
+
|
|
34
|
+
module.exports = plugin;
|
data/postcss-gem-import.js
CHANGED
|
@@ -1,28 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
return {
|
|
5
|
-
postcssPlugin: 'postcss-gem-import',
|
|
6
|
-
Once(root) {
|
|
7
|
-
root.walkAtRules('import', (rule) => {
|
|
8
|
-
const importPath = rule.params.replace(/['"]/g, '');
|
|
9
|
-
|
|
10
|
-
if (importPath.startsWith('gem:')) {
|
|
11
|
-
const gemName = importPath.split('gem:')[1].split('/')[0];
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const gemPath = execSync(`bundle show ${gemName}`, { encoding: 'utf8' }).trim();
|
|
15
|
-
const newPath = importPath.replace(`gem:${gemName}`, gemPath);
|
|
16
|
-
rule.params = `"${newPath}"`;
|
|
17
|
-
} catch (error) {
|
|
18
|
-
throw rule.error(`Failed to resolve gem path for ${gemName}: ${error.message}`);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
plugin.postcss = true;
|
|
27
|
-
|
|
28
|
-
export default plugin;
|
|
1
|
+
// Apps generated before the plugin moved to CommonJS reference this path from
|
|
2
|
+
// their postcss.config.js. Keep it resolving; new apps point at the .cjs file.
|
|
3
|
+
export { default } from "./postcss-gem-import.cjs"
|