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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
import { t } from "../i18n.js";
|
|
2
3
|
|
|
3
4
|
// Connects to data-controller="slim-select"
|
|
4
5
|
//
|
|
@@ -33,28 +34,33 @@ export default class extends Controller {
|
|
|
33
34
|
|
|
34
35
|
#setupSlimSelect() {
|
|
35
36
|
const settings = {};
|
|
36
|
-
const modal = document.querySelector('[data-controller="remote-modal"]');
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (originalPosition === "static") {
|
|
47
|
-
selectWrapper.style.position = "relative";
|
|
48
|
-
this.modifiedSelectWrapper = selectWrapper;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Insert the container right after the select element
|
|
52
|
-
this.element.parentNode.insertBefore(
|
|
53
|
-
this.dropdownContainer,
|
|
54
|
-
this.element.nextSibling
|
|
55
|
-
);
|
|
38
|
+
// Optional locale passthrough: plutonium.js.libraries.slim_select may
|
|
39
|
+
// define placeholderText, searchText, searchPlaceholder and searchingText.
|
|
40
|
+
const strings = t("plutonium.js.libraries.slim_select");
|
|
41
|
+
if (strings && typeof strings === "object") {
|
|
42
|
+
const { placeholderText, searchText, searchPlaceholder, searchingText } = strings;
|
|
43
|
+
Object.assign(settings, { placeholderText, searchText, searchPlaceholder, searchingText });
|
|
44
|
+
Object.keys(settings).forEach((k) => settings[k] === undefined && delete settings[k]);
|
|
45
|
+
}
|
|
56
46
|
|
|
57
|
-
|
|
47
|
+
this.modal = this.element.closest('[data-controller="remote-modal"]');
|
|
48
|
+
|
|
49
|
+
if (this.modal) {
|
|
50
|
+
// Inside a <dialog> opened via showModal(), the dialog lives in the
|
|
51
|
+
// browser's top layer: anything rendered outside it (e.g. on
|
|
52
|
+
// document.body) paints BEHIND it regardless of z-index, and the
|
|
53
|
+
// dialog is transform-animated (slide/scale), so position:fixed
|
|
54
|
+
// descendants are rebased to the dialog's box rather than the
|
|
55
|
+
// viewport. Rendering the dropdown into the select's own container
|
|
56
|
+
// (SlimSelect's other default) instead leaves it clipped by the
|
|
57
|
+
// form section's overflow.
|
|
58
|
+
//
|
|
59
|
+
// So attach the dropdown to the dialog itself and position it
|
|
60
|
+
// absolutely relative to the dialog's box (see #applyModalPositioning).
|
|
61
|
+
// It then escapes every section's overflow while staying inside the
|
|
62
|
+
// top layer, on top of the form. Mirrors the flatpickr controller.
|
|
63
|
+
settings.contentLocation = this.modal;
|
|
58
64
|
settings.contentPosition = "absolute";
|
|
59
65
|
settings.openPosition = "auto";
|
|
60
66
|
}
|
|
@@ -73,125 +79,58 @@ export default class extends Controller {
|
|
|
73
79
|
events: events,
|
|
74
80
|
});
|
|
75
81
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// Bind event handlers for proper cleanup
|
|
80
|
-
this.boundHandleDropdownOpen = this.handleDropdownOpen.bind(this);
|
|
81
|
-
this.boundHandleDropdownClose = this.handleDropdownClose.bind(this);
|
|
82
|
-
|
|
83
|
-
// Add event listeners to properly handle dropdown visibility
|
|
84
|
-
this.element.addEventListener("ss:open", this.boundHandleDropdownOpen);
|
|
85
|
-
this.element.addEventListener("ss:close", this.boundHandleDropdownClose);
|
|
86
|
-
|
|
87
|
-
// Add mutation observer to track aria-expanded attribute
|
|
88
|
-
this.setupAriaObserver();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
handleDropdownPosition() {
|
|
92
|
-
if (this.dropdownContainer) {
|
|
93
|
-
// Reposition dropdown when window resizes or scrolls
|
|
94
|
-
const repositionDropdown = () => {
|
|
95
|
-
const selectRect = this.element.getBoundingClientRect();
|
|
96
|
-
|
|
97
|
-
// Calculate if there's enough space below
|
|
98
|
-
const spaceBelow = window.innerHeight - selectRect.bottom;
|
|
99
|
-
const spaceAbove = selectRect.top;
|
|
100
|
-
|
|
101
|
-
if (spaceBelow < 200 && spaceAbove > spaceBelow) {
|
|
102
|
-
// Position above if not enough space below
|
|
103
|
-
this.dropdownContainer.style.top = "auto";
|
|
104
|
-
this.dropdownContainer.style.bottom = "100%";
|
|
105
|
-
this.dropdownContainer.style.borderRadius = "0.375rem 0.375rem 0 0";
|
|
106
|
-
} else {
|
|
107
|
-
// Position below (default)
|
|
108
|
-
this.dropdownContainer.style.bottom = "auto";
|
|
109
|
-
this.dropdownContainer.style.borderRadius = "0 0 0.375rem 0.375rem";
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
// Initial positioning
|
|
114
|
-
setTimeout(repositionDropdown, 0);
|
|
115
|
-
|
|
116
|
-
// Reposition on events
|
|
117
|
-
window.addEventListener("resize", repositionDropdown);
|
|
118
|
-
window.addEventListener("scroll", repositionDropdown);
|
|
119
|
-
|
|
120
|
-
// Store references for cleanup
|
|
121
|
-
this.repositionDropdown = repositionDropdown;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
handleDropdownOpen() {
|
|
126
|
-
if (this.dropdownContainer) {
|
|
127
|
-
// When dropdown opens, ensure our container is properly sized
|
|
128
|
-
this.dropdownContainer.style.height = "auto";
|
|
129
|
-
this.dropdownContainer.style.overflow = "visible";
|
|
130
|
-
|
|
131
|
-
// Add open class for better CSS targeting
|
|
132
|
-
this.dropdownContainer.classList.add("ss-active");
|
|
133
|
-
|
|
134
|
-
// Ensure this dropdown appears above others
|
|
135
|
-
const allContainers = document.querySelectorAll(".ss-dropdown-container");
|
|
136
|
-
allContainers.forEach((container) => {
|
|
137
|
-
if (container !== this.dropdownContainer) {
|
|
138
|
-
container.style.zIndex = "9999";
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
this.dropdownContainer.style.zIndex = "10000";
|
|
82
|
+
if (this.modal) {
|
|
83
|
+
this.#applyModalPositioning();
|
|
142
84
|
}
|
|
143
85
|
}
|
|
144
86
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
this.handleDropdownOpen();
|
|
190
|
-
} else {
|
|
191
|
-
this.handleDropdownClose();
|
|
192
|
-
}
|
|
87
|
+
// Re-base SlimSelect's dropdown positioning onto the modal's coordinate
|
|
88
|
+
// system. SlimSelect computes document coordinates (rect + window scroll),
|
|
89
|
+
// which are wrong for content parented to a transform-animated <dialog>:
|
|
90
|
+
// an absolutely-positioned child is placed relative to the dialog's box,
|
|
91
|
+
// not the document. We override the two leaf positioners so SlimSelect
|
|
92
|
+
// keeps deciding up-vs-down (and its own open/scroll/resize triggers keep
|
|
93
|
+
// firing), but the actual offsets are measured against the dialog. A
|
|
94
|
+
// capture-phase scroll listener covers scrolls of the dialog's own
|
|
95
|
+
// content, which never reach SlimSelect's window-level scroll listener.
|
|
96
|
+
#applyModalPositioning() {
|
|
97
|
+
const render = this.slimSelect.render;
|
|
98
|
+
const openAbove = render.classes.openAbove;
|
|
99
|
+
const openBelow = render.classes.openBelow;
|
|
100
|
+
|
|
101
|
+
const place = (above) => {
|
|
102
|
+
const mainEl = render.main.main;
|
|
103
|
+
const content = render.content.main;
|
|
104
|
+
const mainHeight = mainEl.offsetHeight;
|
|
105
|
+
const contentHeight = content.offsetHeight;
|
|
106
|
+
|
|
107
|
+
mainEl.classList.remove(openAbove, openBelow);
|
|
108
|
+
content.classList.remove(openAbove, openBelow);
|
|
109
|
+
mainEl.classList.add(above ? openAbove : openBelow);
|
|
110
|
+
content.classList.add(above ? openAbove : openBelow);
|
|
111
|
+
|
|
112
|
+
const inputRect = mainEl.getBoundingClientRect();
|
|
113
|
+
const modalRect = this.modal.getBoundingClientRect();
|
|
114
|
+
|
|
115
|
+
content.style.margin = above
|
|
116
|
+
? "-" + (mainHeight + contentHeight - 1) + "px 0px 0px 0px"
|
|
117
|
+
: "-1px 0px 0px 0px";
|
|
118
|
+
content.style.top =
|
|
119
|
+
inputRect.top + inputRect.height - modalRect.top + "px";
|
|
120
|
+
content.style.left = inputRect.left - modalRect.left + "px";
|
|
121
|
+
content.style.width = inputRect.width + "px";
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
render.moveContentAbove = () => place(true);
|
|
125
|
+
render.moveContentBelow = () => place(false);
|
|
126
|
+
|
|
127
|
+
this.boundModalReposition = () => {
|
|
128
|
+
if (!this.slimSelect) return;
|
|
129
|
+
if (this.slimSelect.settings.isOpen || this.slimSelect.settings.isFullOpen) {
|
|
130
|
+
this.slimSelect.render.moveContent();
|
|
193
131
|
}
|
|
194
|
-
}
|
|
132
|
+
};
|
|
133
|
+
document.addEventListener("scroll", this.boundModalReposition, true);
|
|
195
134
|
}
|
|
196
135
|
|
|
197
136
|
disconnect() {
|
|
@@ -224,7 +163,7 @@ export default class extends Controller {
|
|
|
224
163
|
headers: { Accept: "application/json" },
|
|
225
164
|
signal: signal,
|
|
226
165
|
});
|
|
227
|
-
if (!res.ok) return "
|
|
166
|
+
if (!res.ok) return t("plutonium.js.slim_select.search_failed");
|
|
228
167
|
const json = await res.json();
|
|
229
168
|
const results = Array.isArray(json.results) ? json.results : [];
|
|
230
169
|
return results.map((row) => ({
|
|
@@ -234,7 +173,7 @@ export default class extends Controller {
|
|
|
234
173
|
} catch (e) {
|
|
235
174
|
if (e.name === "AbortError") return [];
|
|
236
175
|
console.warn("[slim-select] typeahead error", e);
|
|
237
|
-
return "
|
|
176
|
+
return t("plutonium.js.slim_select.search_failed");
|
|
238
177
|
}
|
|
239
178
|
}
|
|
240
179
|
|
|
@@ -249,50 +188,14 @@ export default class extends Controller {
|
|
|
249
188
|
}
|
|
250
189
|
|
|
251
190
|
#cleanupSlimSelect() {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
this.element.removeEventListener(
|
|
256
|
-
"ss:open",
|
|
257
|
-
this.boundHandleDropdownOpen
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
if (this.boundHandleDropdownClose) {
|
|
261
|
-
this.element.removeEventListener(
|
|
262
|
-
"ss:close",
|
|
263
|
-
this.boundHandleDropdownClose
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Disconnect observer
|
|
269
|
-
if (this.ariaObserver) {
|
|
270
|
-
this.ariaObserver.disconnect();
|
|
271
|
-
this.ariaObserver = null;
|
|
191
|
+
if (this.boundModalReposition) {
|
|
192
|
+
document.removeEventListener("scroll", this.boundModalReposition, true);
|
|
193
|
+
this.boundModalReposition = null;
|
|
272
194
|
}
|
|
273
195
|
|
|
274
196
|
if (this.slimSelect) {
|
|
275
197
|
this.slimSelect.destroy();
|
|
276
198
|
this.slimSelect = null;
|
|
277
199
|
}
|
|
278
|
-
|
|
279
|
-
// Clean up event listeners
|
|
280
|
-
if (this.repositionDropdown) {
|
|
281
|
-
window.removeEventListener("resize", this.repositionDropdown);
|
|
282
|
-
window.removeEventListener("scroll", this.repositionDropdown);
|
|
283
|
-
this.repositionDropdown = null;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Clean up the dropdown container if it exists
|
|
287
|
-
if (this.dropdownContainer && this.dropdownContainer.parentNode) {
|
|
288
|
-
this.dropdownContainer.parentNode.removeChild(this.dropdownContainer);
|
|
289
|
-
this.dropdownContainer = null;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Restore original positioning if we modified it
|
|
293
|
-
if (this.modifiedSelectWrapper) {
|
|
294
|
-
this.modifiedSelectWrapper.style.position = "";
|
|
295
|
-
this.modifiedSelectWrapper = null;
|
|
296
|
-
}
|
|
297
200
|
}
|
|
298
201
|
}
|
data/src/js/core.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import registerControllers from "./controllers/register_controllers.js"
|
|
2
|
-
|
|
2
|
+
import { t } from "./i18n.js"
|
|
3
|
+
export { registerControllers, t }
|
|
4
|
+
|
|
5
|
+
// Expose the translator so host apps (and inline scripts) can reuse the
|
|
6
|
+
// `plutonium.js.*` strings without importing the bundle.
|
|
7
|
+
if (typeof window !== "undefined") {
|
|
8
|
+
window.Plutonium = window.Plutonium || {}
|
|
9
|
+
window.Plutonium.t = t
|
|
10
|
+
}
|
|
3
11
|
|
|
4
12
|
import "./turbo"
|
data/src/js/i18n.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Translations for Plutonium's bundled JavaScript.
|
|
2
|
+
//
|
|
3
|
+
// Rails serialises the `plutonium.js` subtree of the current locale into
|
|
4
|
+
// `<meta name="pu-i18n" content="...">` in the layout head (see
|
|
5
|
+
// Plutonium::UI::Layout::Base#render_i18n). This module reads that blob
|
|
6
|
+
// lazily, so host apps override any string by defining the key in their own
|
|
7
|
+
// config/locales, exactly as they do for Plutonium's Ruby-side strings.
|
|
8
|
+
//
|
|
9
|
+
// t("plutonium.js.clipboard.copied") // "Copied!"
|
|
10
|
+
// t("plutonium.js.attachment_input.delete_all", {count: 3}) // "Delete 3"
|
|
11
|
+
// t("plutonium.js.libraries.slim_select") // {placeholderText: ...} or the key
|
|
12
|
+
//
|
|
13
|
+
// Lookup rules mirror I18n: `%{name}` placeholders are interpolated from
|
|
14
|
+
// `params`; a `count` param picks the `one` / `other` subkey when the value
|
|
15
|
+
// is a plural object; a missing key falls back to the key itself.
|
|
16
|
+
|
|
17
|
+
const PREFIX = "plutonium.js."
|
|
18
|
+
const BLOB_SELECTOR = 'meta[name="pu-i18n"]'
|
|
19
|
+
|
|
20
|
+
let dictionary = null
|
|
21
|
+
|
|
22
|
+
function load() {
|
|
23
|
+
if (typeof document === "undefined") return {}
|
|
24
|
+
const el = document.querySelector(BLOB_SELECTOR)
|
|
25
|
+
if (!el) return {}
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(el.content) || {}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.warn("[plutonium] could not parse the pu-i18n locale blob", e)
|
|
30
|
+
return {}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Raw value for a key (string, plural object, library object) or undefined.
|
|
35
|
+
export function lookup(key) {
|
|
36
|
+
if (dictionary === null) dictionary = load()
|
|
37
|
+
const path = key.startsWith(PREFIX) ? key.slice(PREFIX.length) : key
|
|
38
|
+
return path.split(".").reduce(
|
|
39
|
+
(node, part) => (node != null && typeof node === "object" ? node[part] : undefined),
|
|
40
|
+
dictionary
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function interpolate(text, params) {
|
|
45
|
+
return text.replace(/%\{(\w+)\}/g, (match, name) =>
|
|
46
|
+
Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : match
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function t(key, params = {}) {
|
|
51
|
+
let value = lookup(key)
|
|
52
|
+
|
|
53
|
+
if (value != null && typeof value === "object" && "count" in params) {
|
|
54
|
+
value = params.count === 1 ? value.one : value.other
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof value === "string") return interpolate(value, params)
|
|
58
|
+
return value === undefined ? key : value
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Turbo Drive replaces the head's <meta> elements on navigation, so the blob
|
|
62
|
+
// changes when the locale does; drop the cache after each render.
|
|
63
|
+
if (typeof document !== "undefined") {
|
|
64
|
+
const reset = () => { dictionary = null }
|
|
65
|
+
document.addEventListener("turbo:load", reset)
|
|
66
|
+
document.addEventListener("turbo:render", reset)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default t
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// The on-demand charts bundle: Chart.js wired into Chartkick.
|
|
2
|
+
//
|
|
3
|
+
// Built as its own entry point (app/assets/plutonium-charts.js) and NOT
|
|
4
|
+
// imported by plutonium.js, so pages without a chart never download it. The
|
|
5
|
+
// `chart` Stimulus controller injects this script the first time a chart card
|
|
6
|
+
// connects (its URL rides the card's data-chart-script-value) and then draws
|
|
7
|
+
// through `window.Chartkick`.
|
|
8
|
+
import Chartkick from "chartkick"
|
|
9
|
+
import "chartkick/chart.js"
|
|
10
|
+
|
|
11
|
+
if (typeof window !== "undefined") {
|
|
12
|
+
window.Chartkick = Chartkick
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default Chartkick
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Themed replacement for Turbo's default window.confirm. The dialog is
|
|
2
2
|
// built lazily and reused so per-call cost is just a textContent swap.
|
|
3
3
|
|
|
4
|
+
import { t } from "../i18n.js";
|
|
5
|
+
|
|
4
6
|
let dialog;
|
|
5
7
|
let messageEl;
|
|
6
8
|
let confirmButton;
|
|
@@ -55,12 +57,12 @@ function ensureDialog() {
|
|
|
55
57
|
cancelButton = document.createElement("button");
|
|
56
58
|
cancelButton.type = "button";
|
|
57
59
|
cancelButton.className = "pu-btn pu-btn-md pu-btn-outline";
|
|
58
|
-
cancelButton.textContent = "
|
|
60
|
+
cancelButton.textContent = t("plutonium.js.turbo_confirm.cancel");
|
|
59
61
|
|
|
60
62
|
confirmButton = document.createElement("button");
|
|
61
63
|
confirmButton.type = "button";
|
|
62
64
|
confirmButton.className = "pu-btn pu-btn-md pu-btn-primary";
|
|
63
|
-
confirmButton.textContent = "
|
|
65
|
+
confirmButton.textContent = t("plutonium.js.turbo_confirm.confirm");
|
|
64
66
|
|
|
65
67
|
footer.appendChild(cancelButton);
|
|
66
68
|
footer.appendChild(confirmButton);
|
|
@@ -79,7 +81,7 @@ async function animateClose() {
|
|
|
79
81
|
|
|
80
82
|
function themedConfirm(message) {
|
|
81
83
|
ensureDialog();
|
|
82
|
-
messageEl.textContent = message || "
|
|
84
|
+
messageEl.textContent = message || t("plutonium.js.are_you_sure");
|
|
83
85
|
|
|
84
86
|
return new Promise((resolve) => {
|
|
85
87
|
let settled = false;
|