alchemy_cms 7.4.10 → 8.0.0.b
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.
Potentially problematic release.
This version of alchemy_cms might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +16 -5
- data/app/assets/builds/alchemy/admin/page-select.css +1 -1
- data/app/assets/builds/alchemy/admin/print.css +1 -1
- data/app/assets/builds/alchemy/admin.css +2 -2
- data/app/assets/builds/alchemy/dark-theme.css +1 -0
- data/app/assets/builds/alchemy/light-theme.css +1 -0
- data/app/assets/builds/alchemy/theme.css +1 -0
- data/app/assets/builds/alchemy/welcome.css +1 -1
- data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
- data/app/assets/builds/tinymce/skins/content/alchemy-dark/content.min.css +1 -0
- data/app/assets/builds/tinymce/skins/ui/alchemy/content.min.css +1 -0
- data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css +1 -1
- data/app/assets/builds/tinymce/skins/ui/alchemy-dark/content.min.css +1 -0
- data/app/assets/builds/tinymce/skins/ui/alchemy-dark/skin.min.css +1 -0
- data/app/assets/config/alchemy_manifest.js +0 -2
- data/app/assets/images/alchemy/element_icons/default.svg +1 -0
- data/app/assets/images/alchemy/icons-sprite.svg +1 -0
- data/app/components/alchemy/admin/element_select.rb +39 -0
- data/app/components/alchemy/admin/resource/applied_filter.rb +29 -0
- data/app/components/alchemy/admin/resource/checkbox_filter.rb +36 -0
- data/app/components/alchemy/admin/resource/datepicker_filter.rb +42 -0
- data/app/components/alchemy/admin/resource/select_filter.rb +43 -0
- data/app/components/alchemy/admin/toolbar_button.rb +5 -2
- data/app/components/alchemy/ingredients/number_view.rb +18 -0
- data/app/controllers/alchemy/admin/attachments_controller.rb +10 -15
- data/app/controllers/alchemy/admin/clipboard_controller.rb +2 -6
- data/app/controllers/alchemy/admin/elements_controller.rb +3 -1
- data/app/controllers/alchemy/admin/languages_controller.rb +1 -1
- data/app/controllers/alchemy/admin/pages_controller.rb +15 -15
- data/app/controllers/alchemy/admin/pictures_controller.rb +27 -37
- data/app/controllers/alchemy/admin/resources_controller.rb +16 -106
- data/app/controllers/alchemy/attachments_controller.rb +43 -14
- data/app/controllers/alchemy/messages_controller.rb +1 -1
- data/app/controllers/alchemy/pages_controller.rb +26 -4
- data/app/controllers/concerns/alchemy/admin/resource_filter.rb +93 -0
- data/app/decorators/alchemy/element_editor.rb +5 -48
- data/app/decorators/alchemy/ingredient_editor.rb +3 -53
- data/app/helpers/alchemy/admin/attachments_helper.rb +5 -5
- data/app/helpers/alchemy/admin/base_helper.rb +14 -84
- data/app/helpers/alchemy/admin/pages_helper.rb +1 -1
- data/app/helpers/alchemy/base_helper.rb +0 -30
- data/app/helpers/alchemy/elements_block_helper.rb +0 -14
- data/app/helpers/alchemy/pages_helper.rb +1 -1
- data/{lib → app/helpers}/alchemy/resources_helper.rb +5 -45
- data/app/javascript/alchemy_admin/components/action.js +2 -0
- data/app/javascript/alchemy_admin/components/alchemy_html_element.js +3 -3
- data/app/javascript/alchemy_admin/components/auto_submit.js +20 -0
- data/app/javascript/alchemy_admin/components/datepicker.js +18 -7
- data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +8 -7
- data/app/javascript/alchemy_admin/components/element_editor.js +25 -15
- data/app/javascript/alchemy_admin/components/element_select.js +43 -0
- data/app/javascript/alchemy_admin/components/index.js +3 -0
- data/app/javascript/alchemy_admin/components/link_buttons.js +6 -2
- data/app/javascript/alchemy_admin/components/remote_select.js +9 -2
- data/app/javascript/alchemy_admin/components/tags_autocomplete.js +5 -1
- data/app/javascript/alchemy_admin/components/tinymce.js +93 -14
- data/app/javascript/alchemy_admin/components/update_check.js +42 -0
- data/app/javascript/alchemy_admin/components/uploader/file_upload.js +15 -8
- data/app/javascript/alchemy_admin/components/uploader/progress.js +12 -6
- data/app/javascript/alchemy_admin/components/uploader.js +4 -2
- data/app/javascript/alchemy_admin/confirm_dialog.js +27 -57
- data/app/javascript/alchemy_admin/dialog.js +1 -1
- data/app/javascript/alchemy_admin/dirty.js +3 -2
- data/app/javascript/alchemy_admin/file_editors.js +1 -1
- data/app/javascript/alchemy_admin/i18n.js +15 -16
- data/app/javascript/alchemy_admin/image_loader.js +4 -2
- data/app/javascript/alchemy_admin/initializer.js +1 -49
- data/app/javascript/alchemy_admin/picture_editors.js +7 -4
- data/app/javascript/alchemy_admin/picture_selector.js +4 -4
- data/app/javascript/alchemy_admin/utils/ajax.js +51 -44
- data/app/javascript/alchemy_admin.js +3 -8
- data/app/jobs/alchemy/delete_picture_job.rb +12 -0
- data/app/models/alchemy/admin/filters/base.rb +38 -0
- data/app/models/alchemy/admin/filters/checkbox.rb +24 -0
- data/app/models/alchemy/admin/filters/datepicker.rb +53 -0
- data/app/models/alchemy/admin/filters/select.rb +70 -0
- data/app/models/alchemy/admin/resource_name.rb +27 -0
- data/app/models/alchemy/attachment.rb +49 -44
- data/app/models/alchemy/base_record.rb +2 -0
- data/app/models/alchemy/element/definitions.rb +1 -1
- data/app/models/alchemy/element/element_ingredients.rb +6 -6
- data/app/models/alchemy/element/presenters.rb +3 -12
- data/app/models/alchemy/element.rb +10 -27
- data/app/models/alchemy/element_definition.rb +190 -0
- data/app/models/alchemy/ingredient.rb +10 -43
- data/app/models/alchemy/ingredient_definition.rb +134 -0
- data/app/models/alchemy/ingredient_validator.rb +7 -3
- data/app/models/alchemy/ingredients/number.rb +19 -0
- data/app/models/alchemy/language.rb +2 -21
- data/app/models/alchemy/message.rb +3 -7
- data/app/models/alchemy/node.rb +1 -1
- data/app/models/alchemy/page/{page_layouts.rb → definitions.rb} +12 -19
- data/app/models/alchemy/page/fixed_attributes.rb +1 -1
- data/app/models/alchemy/page/page_elements.rb +13 -14
- data/app/models/alchemy/page/page_naming.rb +3 -11
- data/app/models/alchemy/page/page_natures.rb +20 -15
- data/app/models/alchemy/page/page_scopes.rb +1 -1
- data/app/models/alchemy/page.rb +12 -39
- data/app/models/alchemy/page_definition.rb +115 -0
- data/app/models/alchemy/picture.rb +71 -99
- data/app/models/alchemy/picture_variant.rb +115 -5
- data/{lib → app/models}/alchemy/resource.rb +4 -18
- data/{lib → app/models}/alchemy/searchable_resource.rb +15 -0
- data/app/models/alchemy/site/layout.rb +5 -5
- data/app/models/alchemy/site.rb +1 -21
- data/app/models/alchemy/storage_adapter/active_storage/attachment_url.rb +41 -0
- data/app/models/alchemy/storage_adapter/active_storage/picture_url.rb +55 -0
- data/app/models/alchemy/storage_adapter/active_storage/preprocessor.rb +40 -0
- data/app/models/alchemy/storage_adapter/active_storage.rb +173 -0
- data/app/models/alchemy/{attachment/url.rb → storage_adapter/dragonfly/attachment_url.rb} +12 -12
- data/app/models/alchemy/storage_adapter/dragonfly/picture_url.rb +75 -0
- data/app/models/alchemy/{picture → storage_adapter/dragonfly}/preprocessor.rb +4 -4
- data/app/models/alchemy/storage_adapter/dragonfly.rb +205 -0
- data/app/models/alchemy/storage_adapter.rb +74 -0
- data/app/models/concerns/alchemy/picture_thumbnails.rb +19 -6
- data/app/models/concerns/alchemy/relatable_resource.rb +28 -0
- data/app/serializers/alchemy/element_serializer.rb +0 -1
- data/app/services/alchemy/dragonfly_to_image_processing.rb +100 -0
- data/app/stylesheets/alchemy/_custom-properties.scss +162 -0
- data/app/stylesheets/alchemy/_defaults.scss +3 -0
- data/app/stylesheets/alchemy/_extends.scss +69 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/_mixins.scss +40 -68
- data/app/stylesheets/alchemy/_themes.scss +540 -0
- data/app/stylesheets/alchemy/_variables.scss +5 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/archive.scss +45 -42
- data/app/stylesheets/alchemy/admin/attachments.scss +17 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/base.scss +20 -15
- data/app/stylesheets/alchemy/admin/buttons.scss +135 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/clipboard.scss +2 -2
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dashboard.scss +13 -16
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/dialogs.scss +33 -16
- data/app/stylesheets/alchemy/admin/element-select.scss +11 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/elements.scss +239 -133
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/errors.scss +5 -5
- data/app/stylesheets/alchemy/admin/filters.scss +57 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flatpickr.scss +54 -76
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/form_fields.scss +10 -11
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/forms.scss +28 -21
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/frame.scss +20 -18
- data/app/stylesheets/alchemy/admin/hints.scss +5 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/icons.scss +1 -1
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/image_library.scss +21 -61
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/images.scss +1 -1
- data/app/stylesheets/alchemy/admin/labels.scss +5 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/lists.scss +3 -3
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/navigation.scss +55 -59
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/node-select.scss +1 -10
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/nodes.scss +21 -18
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/notices.scss +20 -19
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/page-select.scss +18 -2
- data/app/stylesheets/alchemy/admin/pagination.scss +137 -0
- data/app/stylesheets/alchemy/admin/preview_window.scss +46 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/print.scss +1 -1
- data/app/stylesheets/alchemy/admin/resource_info.scss +148 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/search.scss +10 -7
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/selects.scss +85 -46
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/shoelace.scss +37 -68
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/sitemap.scss +39 -34
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tables.scss +9 -7
- data/app/stylesheets/alchemy/admin/tags.scss +143 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/toolbar.scss +6 -6
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/typography.scss +3 -6
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/upload.scss +7 -5
- data/app/stylesheets/alchemy/admin.scss +44 -0
- data/app/stylesheets/alchemy/dark-theme.scss +5 -0
- data/app/stylesheets/alchemy/light-theme.scss +6 -0
- data/app/stylesheets/alchemy/theme.scss +13 -0
- data/app/stylesheets/alchemy/welcome.scss +75 -0
- data/app/stylesheets/tinymce/skins/content/alchemy/content.scss +69 -0
- data/app/{assets/stylesheets/tinymce/skins/content/alchemy → stylesheets/tinymce/skins/content/alchemy-dark}/content.scss +12 -12
- data/app/stylesheets/tinymce/skins/ui/alchemy/content.scss +1 -0
- data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/skin.scss +158 -176
- data/app/stylesheets/tinymce/skins/ui/alchemy-dark/content.scss +1 -0
- data/app/stylesheets/tinymce/skins/ui/alchemy-dark/skin.scss +3784 -0
- data/app/views/alchemy/admin/attachments/_files_list.html.erb +22 -12
- data/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb +1 -1
- data/app/views/alchemy/admin/attachments/assign.js.erb +4 -3
- data/app/views/alchemy/admin/attachments/show.html.erb +55 -43
- data/app/views/alchemy/admin/{elements/_clipboard_button.html.erb → clipboard/_button.html.erb} +3 -5
- data/app/views/alchemy/admin/clipboard/_update_nested_element_button.turbo_stream.erb +11 -0
- data/app/views/alchemy/admin/clipboard/clear.turbo_stream.erb +4 -0
- data/app/views/alchemy/admin/clipboard/index.html.erb +15 -13
- data/app/views/alchemy/admin/clipboard/insert.turbo_stream.erb +18 -0
- data/app/views/alchemy/admin/clipboard/remove.turbo_stream.erb +9 -0
- data/app/views/alchemy/admin/crop.html.erb +1 -1
- data/app/views/alchemy/admin/dashboard/info.html.erb +17 -31
- data/app/views/alchemy/admin/elements/_element.html.erb +4 -8
- data/app/views/alchemy/admin/elements/_form.html.erb +9 -9
- data/app/views/alchemy/admin/elements/_header.html.erb +5 -1
- data/app/views/alchemy/admin/elements/_toolbar.html.erb +4 -6
- data/app/views/alchemy/admin/elements/create.turbo_stream.erb +2 -1
- data/app/views/alchemy/admin/elements/index.html.erb +2 -2
- data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +3 -16
- data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +0 -9
- data/app/views/alchemy/admin/languages/_form.html.erb +1 -1
- data/app/views/alchemy/admin/languages/_table.html.erb +1 -1
- data/app/views/alchemy/admin/languages/index.html.erb +5 -2
- data/app/views/alchemy/admin/layoutpages/index.html.erb +1 -12
- data/app/views/alchemy/admin/pages/_form.html.erb +2 -2
- data/app/views/alchemy/admin/pages/_page.html.erb +2 -3
- data/app/views/alchemy/admin/pages/_toolbar.html.erb +1 -15
- data/app/views/alchemy/admin/pages/index.html.erb +1 -1
- data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +9 -12
- data/app/views/alchemy/admin/partials/_search_form.html.erb +4 -9
- data/app/views/alchemy/admin/pictures/_archive.html.erb +16 -29
- data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +3 -7
- data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +1 -1
- data/app/views/alchemy/admin/pictures/_form.html.erb +1 -1
- data/app/views/alchemy/admin/pictures/_infos.html.erb +21 -52
- data/app/views/alchemy/admin/pictures/_library_sidebar.html.erb +7 -0
- data/app/views/alchemy/admin/pictures/_picture.html.erb +14 -20
- data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +20 -16
- data/app/views/alchemy/admin/pictures/_sorting_select.html.erb +13 -0
- data/app/views/alchemy/admin/pictures/_tag_list.html.erb +1 -1
- data/app/views/alchemy/admin/pictures/edit_multiple.html.erb +1 -6
- data/app/views/alchemy/admin/pictures/index.html.erb +5 -19
- data/app/views/alchemy/admin/pictures/show.html.erb +10 -5
- data/app/views/alchemy/admin/resources/_applied_filters.html.erb +8 -0
- data/app/views/alchemy/admin/resources/_filter_bar.html.erb +5 -25
- data/app/views/alchemy/admin/resources/_pagination.html.erb +6 -0
- data/app/views/alchemy/admin/resources/_per_page_select.html.erb +4 -2
- data/app/views/alchemy/admin/resources/_resource_table.html.erb +1 -1
- data/app/views/alchemy/admin/resources/_resource_usage_info.html.erb +36 -0
- data/app/views/alchemy/admin/resources/_table_header.html.erb +1 -15
- data/app/views/alchemy/admin/sites/index.html.erb +5 -1
- data/app/views/alchemy/admin/styleguide/index.html.erb +118 -58
- data/app/views/alchemy/admin/tags/index.html.erb +1 -1
- data/app/views/alchemy/admin/tinymce/_setup.html.erb +7 -7
- data/app/{javascript/alchemy_admin/locales/en.js → views/alchemy/admin/translations/_en.js} +5 -2
- data/app/views/alchemy/admin/uploader/_button.html.erb +1 -1
- data/app/views/alchemy/admin/uploader/_setup.html.erb +4 -4
- data/app/views/alchemy/base/error_notice.html.erb +1 -1
- data/app/views/alchemy/ingredients/_number_editor.html.erb +24 -0
- data/app/views/alchemy/no_index.html.erb +31 -0
- data/app/views/alchemy/welcome.html.erb +12 -10
- data/app/views/kaminari/alchemy/_first_page.html.erb +5 -3
- data/app/views/kaminari/alchemy/_last_page.html.erb +5 -3
- data/app/views/kaminari/alchemy/_next_page.html.erb +5 -3
- data/app/views/kaminari/alchemy/_paginator.html.erb +18 -13
- data/app/views/kaminari/alchemy/_prev_page.html.erb +5 -3
- data/app/views/layouts/alchemy/admin.html.erb +25 -24
- data/config/alchemy/config.yml +3 -2
- data/config/initializers/dragonfly.rb +0 -1
- data/config/initializers/mime_types.rb +1 -0
- data/config/locales/alchemy.en.yml +57 -21
- data/config/routes.rb +0 -2
- data/db/migrate/20250905140323_add_created_at_index_to_pictures_and_attachments.rb +14 -0
- data/lib/alchemy/admin/preview_url.rb +4 -5
- data/lib/alchemy/cache_digests/template_tracker.rb +6 -9
- data/lib/alchemy/config_missing.rb +14 -0
- data/lib/alchemy/configuration/base_option.rb +24 -0
- data/lib/alchemy/configuration/boolean_option.rb +16 -0
- data/lib/alchemy/configuration/class_option.rb +15 -0
- data/lib/alchemy/configuration/class_set_option.rb +46 -0
- data/lib/alchemy/configuration/integer_list_option.rb +13 -0
- data/lib/alchemy/configuration/integer_option.rb +12 -0
- data/lib/alchemy/configuration/list_option.rb +22 -0
- data/lib/alchemy/configuration/regexp_option.rb +11 -0
- data/lib/alchemy/configuration/string_list_option.rb +13 -0
- data/lib/alchemy/configuration/string_option.rb +11 -0
- data/lib/alchemy/configuration.rb +115 -0
- data/lib/alchemy/configuration_methods.rb +3 -1
- data/lib/alchemy/configurations/default_language.rb +12 -0
- data/lib/alchemy/configurations/default_site.rb +10 -0
- data/lib/alchemy/configurations/format_matchers.rb +11 -0
- data/lib/alchemy/configurations/mailer.rb +16 -0
- data/lib/alchemy/configurations/main.rb +223 -0
- data/lib/alchemy/configurations/page_cache.rb +19 -0
- data/lib/alchemy/configurations/preview.rb +32 -0
- data/lib/alchemy/configurations/sitemap.rb +10 -0
- data/lib/alchemy/configurations/uploader.rb +34 -0
- data/lib/alchemy/engine.rb +81 -24
- data/lib/alchemy/hints.rb +3 -7
- data/lib/alchemy/install/tasks.rb +0 -12
- data/lib/alchemy/on_page_layout.rb +2 -2
- data/lib/alchemy/propshaft/tinymce_asset.rb +15 -0
- data/lib/alchemy/seeder.rb +2 -2
- data/lib/alchemy/tasks/tidy.rb +18 -0
- data/lib/alchemy/tasks/usage.rb +4 -4
- data/lib/alchemy/test_support/config_stubbing.rb +1 -7
- data/lib/alchemy/test_support/factories/attachment_factory.rb +13 -2
- data/lib/alchemy/test_support/factories/language_factory.rb +1 -1
- data/lib/alchemy/test_support/factories/page_factory.rb +2 -3
- data/lib/alchemy/test_support/factories/picture_factory.rb +31 -2
- data/lib/alchemy/test_support/factories/site_factory.rb +2 -2
- data/lib/alchemy/test_support/having_crop_action_examples.rb +2 -2
- data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +80 -26
- data/lib/alchemy/test_support/relatable_resource_examples.rb +58 -0
- data/lib/alchemy/test_support/shared_ingredient_examples.rb +5 -5
- data/lib/alchemy/tinymce.rb +0 -1
- data/lib/alchemy/upgrader/eight_zero.rb +14 -0
- data/lib/alchemy/upgrader.rb +33 -20
- data/lib/alchemy/version.rb +1 -1
- data/lib/alchemy.rb +185 -172
- data/lib/alchemy_cms.rb +1 -7
- data/lib/generators/alchemy/ingredient/ingredient_generator.rb +0 -3
- data/lib/generators/alchemy/install/files/_article.html.erb +6 -4
- data/lib/generators/alchemy/install/files/alchemy.en.yml +22 -3
- data/lib/generators/alchemy/install/files/application.html.erb +5 -0
- data/lib/generators/alchemy/install/install_generator.rb +25 -23
- data/lib/generators/alchemy/install/templates/alchemy.rb.tt +200 -0
- data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +0 -1
- data/lib/generators/alchemy/install/templates/elements.yml.tt +3 -1
- data/lib/generators/alchemy/install/templates/menus.yml.tt +1 -1
- data/lib/generators/alchemy/install/templates/page_layouts.yml.tt +2 -2
- data/lib/generators/alchemy/page_layouts/page_layouts_generator.rb +2 -2
- data/lib/tasks/alchemy/assets.rake +14 -0
- data/lib/tasks/alchemy/tidy.rake +6 -0
- data/lib/tasks/alchemy/upgrade.rake +12 -47
- data/vendor/assets/stylesheets/tinymce/skins/content/dark/content.min.css +1 -0
- data/vendor/assets/stylesheets/tinymce/skins/content/default/content.min.css +1 -0
- data/vendor/assets/stylesheets/tinymce/skins/ui/oxide/skin.min.css +1 -0
- data/vendor/assets/stylesheets/tinymce/skins/ui/oxide-dark/content.min.css +1 -0
- data/vendor/assets/stylesheets/tinymce/skins/ui/oxide-dark/skin.min.css +1 -0
- data/vendor/javascript/clipboard.min.js +1 -1
- data/vendor/javascript/cropperjs.min.js +1 -1
- data/vendor/javascript/handlebars.min.js +3 -3
- data/vendor/javascript/jquery.min.js +1 -1
- data/vendor/javascript/select2.min.js +3 -3
- data/vendor/javascript/shoelace.min.js +92 -76
- data/vendor/javascript/sortable.min.js +2 -2
- data/vendor/javascript/tinymce.min.js +1 -1
- data/vendor/javascript/ungap-custom-elements.min.js +2 -2
- metadata +223 -208
- data/CHANGELOG.md +0 -2041
- data/CODE_OF_CONDUCT.md +0 -13
- data/CONTRIBUTING.md +0 -73
- data/Gemfile +0 -71
- data/Rakefile +0 -102
- data/SECURITY.md +0 -13
- data/alchemy_cms.gemspec +0 -88
- data/app/assets/builds/alchemy/admin/page-select.css.map +0 -1
- data/app/assets/builds/alchemy/admin/print.css.map +0 -1
- data/app/assets/builds/alchemy/admin.css.map +0 -1
- data/app/assets/builds/alchemy/custom-properties.css +0 -1
- data/app/assets/builds/alchemy/custom-properties.css.map +0 -1
- data/app/assets/builds/alchemy/welcome.css.map +0 -1
- data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css.map +0 -1
- data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css.map +0 -1
- data/app/assets/javascripts/alchemy/admin.js +0 -10
- data/app/assets/stylesheets/alchemy/_defaults.scss +0 -3
- data/app/assets/stylesheets/alchemy/_deprecated_variables.scss +0 -45
- data/app/assets/stylesheets/alchemy/_deprecation.scss +0 -17
- data/app/assets/stylesheets/alchemy/_extends.scss +0 -62
- data/app/assets/stylesheets/alchemy/_variables.scss +0 -201
- data/app/assets/stylesheets/alchemy/admin/attachments.scss +0 -40
- data/app/assets/stylesheets/alchemy/admin/buttons.scss +0 -123
- data/app/assets/stylesheets/alchemy/admin/hints.scss +0 -5
- data/app/assets/stylesheets/alchemy/admin/labels.scss +0 -3
- data/app/assets/stylesheets/alchemy/admin/pagination.scss +0 -92
- data/app/assets/stylesheets/alchemy/admin/preview_window.scss +0 -33
- data/app/assets/stylesheets/alchemy/admin/resource_info.scss +0 -42
- data/app/assets/stylesheets/alchemy/admin/tags.scss +0 -158
- data/app/assets/stylesheets/alchemy/admin.scss +0 -42
- data/app/assets/stylesheets/alchemy/custom-properties.css +0 -98
- data/app/assets/stylesheets/alchemy/welcome.scss +0 -57
- data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.css +0 -711
- data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.css +0 -705
- data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.inline.min.css +0 -7
- data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.min.css +0 -7
- data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.css +0 -29
- data/app/assets/stylesheets/tinymce/skins/ui/alchemy/content.mobile.min.css +0 -7
- data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.css +0 -677
- data/app/assets/stylesheets/tinymce/skins/ui/alchemy/skin.mobile.min.css +0 -7
- data/app/controllers/alchemy/elements_controller.rb +0 -32
- data/app/helpers/alchemy/admin/elements_helper.rb +0 -25
- data/app/models/alchemy/element/dom_id.rb +0 -31
- data/app/models/alchemy/picture/calculations.rb +0 -49
- data/app/models/alchemy/picture/transformations.rb +0 -115
- data/app/models/alchemy/picture/url.rb +0 -54
- data/app/views/alchemy/admin/attachments/destroy.js.erb +0 -1
- data/app/views/alchemy/admin/clipboard/clear.js.erb +0 -3
- data/app/views/alchemy/admin/clipboard/insert.js.erb +0 -29
- data/app/views/alchemy/admin/clipboard/remove.js.erb +0 -10
- data/app/views/alchemy/admin/resources/_filter.html.erb +0 -12
- data/app/views/alchemy/admin/resources/_resource.html.erb +0 -34
- data/app/views/alchemy/admin/resources/_table.html.erb +0 -29
- data/app/views/alchemy/elements/show.html.erb +0 -1
- data/app/views/alchemy/elements/show.js.erb +0 -1
- data/app/views/alchemy/ingredients/_audio_view.html.erb +0 -1
- data/app/views/alchemy/ingredients/_boolean_view.html.erb +0 -1
- data/app/views/alchemy/ingredients/_datetime_view.html.erb +0 -3
- data/app/views/alchemy/ingredients/_file_view.html.erb +0 -4
- data/app/views/alchemy/ingredients/_headline_view.html.erb +0 -4
- data/app/views/alchemy/ingredients/_html_view.html.erb +0 -1
- data/app/views/alchemy/ingredients/_link_view.html.erb +0 -4
- data/app/views/alchemy/ingredients/_node_view.html.erb +0 -1
- data/app/views/alchemy/ingredients/_page_view.html.erb +0 -1
- data/app/views/alchemy/ingredients/_picture_view.html.erb +0 -4
- data/app/views/alchemy/ingredients/_richtext_view.html.erb +0 -3
- data/app/views/alchemy/ingredients/_select_view.html.erb +0 -1
- data/app/views/alchemy/ingredients/_text_view.html.erb +0 -4
- data/app/views/alchemy/ingredients/_video_view.html.erb +0 -3
- data/babel.config.js +0 -12
- data/bin/importmap +0 -4
- data/bin/rails +0 -9
- data/bin/rspec +0 -3
- data/bin/setup +0 -30
- data/bin/start +0 -17
- data/bun.lockb +0 -0
- data/bundles/shoelace.js +0 -12
- data/bundles/tinymce.js +0 -22
- data/config/initializers/assets.rb +0 -4
- data/eslint.config.js +0 -17
- data/lib/alchemy/config.rb +0 -114
- data/lib/alchemy/element_definition.rb +0 -73
- data/lib/alchemy/page_layout.rb +0 -73
- data/lib/alchemy/resource_filter.rb +0 -40
- data/lib/alchemy/upgrader/seven_point_four.rb +0 -26
- data/lib/alchemy/upgrader/seven_point_three.rb +0 -52
- data/lib/alchemy/upgrader/tasks/.keep +0 -0
- data/lib/generators/alchemy/ingredient/templates/view.html.erb +0 -1
- data/lib/generators/alchemy/install/files/alchemy_admin.js +0 -1
- data/lib/generators/alchemy/install/files/all.js +0 -11
- data/lib/generators/alchemy/install/files/article.css +0 -25
- data/rollup.config.mjs +0 -108
- data/vendor/assets/images/remixicon.symbol.svg +0 -11
- /data/app/{assets/stylesheets → stylesheets}/alchemy/_fonts.scss +0 -0
- /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/attachment-select.scss +0 -0
- /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flash.scss +0 -0
- /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/list_filter.scss +0 -0
- /data/app/{assets/stylesheets → stylesheets}/alchemy/admin/spinner.scss +0 -0
- /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/skintool.json +0 -0
- /data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/fonts/tinymce-mobile.woff +0 -0
@@ -28,33 +28,12 @@ module Alchemy
|
|
28
28
|
large: "240x180"
|
29
29
|
}.with_indifferent_access.freeze
|
30
30
|
|
31
|
-
CONVERTIBLE_FILE_FORMATS = %w[gif jpg jpeg png webp].freeze
|
32
|
-
|
33
|
-
TRANSFORMATION_OPTIONS = [
|
34
|
-
:crop,
|
35
|
-
:crop_from,
|
36
|
-
:crop_size,
|
37
|
-
:flatten,
|
38
|
-
:format,
|
39
|
-
:quality,
|
40
|
-
:size,
|
41
|
-
:upsample
|
42
|
-
]
|
43
|
-
|
44
31
|
include Alchemy::Logger
|
45
32
|
include Alchemy::NameConversions
|
46
33
|
include Alchemy::Taggable
|
47
34
|
include Alchemy::TouchElements
|
48
|
-
include
|
49
|
-
|
50
|
-
has_many :picture_ingredients,
|
51
|
-
class_name: "Alchemy::Ingredients::Picture",
|
52
|
-
foreign_key: "related_object_id",
|
53
|
-
inverse_of: :related_object
|
35
|
+
include Alchemy::RelatableResource
|
54
36
|
|
55
|
-
has_many :elements, through: :picture_ingredients
|
56
|
-
has_many :pages, through: :elements
|
57
|
-
has_many :thumbs, class_name: "Alchemy::PictureThumb", dependent: :destroy
|
58
37
|
has_many :descriptions, class_name: "Alchemy::PictureDescription", dependent: :destroy
|
59
38
|
|
60
39
|
accepts_nested_attributes_for :descriptions, allow_destroy: true, reject_if: ->(attr) { attr[:text].blank? }
|
@@ -72,7 +51,7 @@ module Alchemy
|
|
72
51
|
|
73
52
|
# Image preprocessing class
|
74
53
|
def self.preprocessor_class
|
75
|
-
@_preprocessor_class ||=
|
54
|
+
@_preprocessor_class ||= Alchemy.storage_adapter.preprocessor_class
|
76
55
|
end
|
77
56
|
|
78
57
|
# Set a image preprocessing class
|
@@ -84,56 +63,40 @@ module Alchemy
|
|
84
63
|
@_preprocessor_class = klass
|
85
64
|
end
|
86
65
|
|
87
|
-
|
88
|
-
dragonfly_accessor :image_file, app: :alchemy_pictures do
|
89
|
-
# Preprocess after uploading the picture
|
90
|
-
after_assign do |image|
|
91
|
-
if has_convertible_format?
|
92
|
-
self.class.preprocessor_class.new(image).call
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
before_save :sanitize_image_file_name
|
98
|
-
# Create important thumbnails upfront
|
99
|
-
after_create -> { PictureThumb.generate_thumbs!(self) if has_convertible_format? }
|
66
|
+
include Alchemy.storage_adapter.picture_class_methods
|
100
67
|
|
101
68
|
# We need to define this method here to have it available in the validations below.
|
102
69
|
class << self
|
103
70
|
def allowed_filetypes
|
104
|
-
|
71
|
+
Alchemy.config.uploader.allowed_filetypes.alchemy_pictures
|
105
72
|
end
|
106
73
|
end
|
107
74
|
|
108
75
|
validates_presence_of :image_file
|
109
|
-
validates_size_of :image_file, maximum:
|
110
|
-
|
111
|
-
of: :image_file,
|
112
|
-
in: allowed_filetypes,
|
113
|
-
case_sensitive: false,
|
114
|
-
message: Alchemy.t("not a valid image")
|
76
|
+
validates_size_of :image_file, maximum: Alchemy.config.uploader.file_size_limit.megabytes
|
77
|
+
validate :image_file_type_allowed, if: -> { image_file.present? }
|
115
78
|
|
116
|
-
stampable stamper_class_name: Alchemy.
|
79
|
+
stampable stamper_class_name: Alchemy.user_class_name
|
117
80
|
|
118
81
|
scope :named, ->(name) { where("#{table_name}.name LIKE ?", "%#{name}%") }
|
119
82
|
scope :recent, -> { where("#{table_name}.created_at > ?", Time.current - 24.hours).order(:created_at) }
|
120
|
-
scope :deletable, -> do
|
121
|
-
where(
|
122
|
-
"#{table_name}.id NOT IN (SELECT related_object_id FROM alchemy_ingredients WHERE related_object_id IS NOT NULL AND related_object_type = ?)",
|
123
|
-
name
|
124
|
-
)
|
125
|
-
end
|
126
83
|
scope :without_tag, -> { left_outer_joins(:taggings).where(gutentag_taggings: {id: nil}) }
|
127
|
-
scope :by_file_format, ->(
|
84
|
+
scope :by_file_format, ->(file_format) do
|
85
|
+
Alchemy.storage_adapter.by_file_format_scope(file_format)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Case insensitive Ransack searching and sorting for name attribute
|
89
|
+
ransacker :name, type: :string do
|
90
|
+
arel_table[:name].lower
|
91
|
+
end
|
128
92
|
|
129
93
|
# Class methods
|
130
94
|
|
131
95
|
class << self
|
132
96
|
# The class used to generate URLs for pictures
|
133
97
|
#
|
134
|
-
# @see Alchemy::Picture::Url
|
135
98
|
def url_class
|
136
|
-
@_url_class ||= Alchemy
|
99
|
+
@_url_class ||= Alchemy.storage_adapter.picture_url_class
|
137
100
|
end
|
138
101
|
|
139
102
|
# Set a different picture url class
|
@@ -143,22 +106,16 @@ module Alchemy
|
|
143
106
|
@_url_class = klass
|
144
107
|
end
|
145
108
|
|
146
|
-
def
|
147
|
-
|
148
|
-
[
|
149
|
-
{
|
150
|
-
name: :by_file_format,
|
151
|
-
values: @_file_formats
|
152
|
-
},
|
153
|
-
{
|
154
|
-
name: :misc,
|
155
|
-
values: %w[recent last_upload without_tag deletable]
|
156
|
-
}
|
157
|
-
]
|
109
|
+
def searchable_alchemy_resource_attributes
|
110
|
+
Alchemy.storage_adapter.searchable_alchemy_resource_attributes(name)
|
158
111
|
end
|
159
112
|
|
160
|
-
def
|
161
|
-
|
113
|
+
def ransackable_attributes(_auth_object = nil)
|
114
|
+
Alchemy.storage_adapter.ransackable_attributes(name)
|
115
|
+
end
|
116
|
+
|
117
|
+
def ransackable_associations(_auth_object = nil)
|
118
|
+
Alchemy.storage_adapter.ransackable_associations(name)
|
162
119
|
end
|
163
120
|
|
164
121
|
def last_upload
|
@@ -167,33 +124,30 @@ module Alchemy
|
|
167
124
|
|
168
125
|
Picture.where(upload_hash: last_picture.upload_hash)
|
169
126
|
end
|
127
|
+
|
128
|
+
def ransackable_scopes(_auth_object = nil)
|
129
|
+
[:by_file_format, :recent, :last_upload, :without_tag, :deletable]
|
130
|
+
end
|
131
|
+
|
132
|
+
def file_formats(scope = all)
|
133
|
+
Alchemy.storage_adapter.file_formats(name, scope:)
|
134
|
+
end
|
170
135
|
end
|
171
136
|
|
172
137
|
# Instance methods
|
173
138
|
|
174
139
|
# Returns an url (or relative path) to a processed image for use inside an image_tag helper.
|
175
140
|
#
|
176
|
-
# Any additional options are passed to the url method, so you can add params to your url.
|
177
|
-
#
|
178
141
|
# Example:
|
179
142
|
#
|
180
143
|
# <%= image_tag picture.url(size: '320x200', format: 'png') %>
|
181
144
|
#
|
182
|
-
# @see Alchemy::PictureVariant#call for transformation options
|
183
|
-
# @see Alchemy::Picture::Url#call for url options
|
184
145
|
# @return [String|Nil]
|
185
146
|
def url(options = {})
|
186
147
|
return unless image_file
|
187
148
|
|
188
|
-
|
189
|
-
|
190
|
-
options.except(*TRANSFORMATION_OPTIONS).merge(
|
191
|
-
basename: name,
|
192
|
-
ext: variant.render_format,
|
193
|
-
name: name
|
194
|
-
)
|
195
|
-
)
|
196
|
-
rescue ::Dragonfly::Job::Fetch::NotFound => e
|
149
|
+
self.class.url_class.new(self).call(options)
|
150
|
+
rescue Alchemy.storage_adapter.rescuable_errors => e
|
197
151
|
log_warning(e.message)
|
198
152
|
nil
|
199
153
|
end
|
@@ -208,7 +162,7 @@ module Alchemy
|
|
208
162
|
|
209
163
|
url(
|
210
164
|
flatten: true,
|
211
|
-
format:
|
165
|
+
format: image_file_extension || "jpg",
|
212
166
|
size: size
|
213
167
|
)
|
214
168
|
end
|
@@ -242,18 +196,12 @@ module Alchemy
|
|
242
196
|
end
|
243
197
|
end
|
244
198
|
|
245
|
-
# Returns the suffix of the filename.
|
246
|
-
#
|
247
|
-
def suffix
|
248
|
-
image_file.ext
|
249
|
-
end
|
250
|
-
|
251
199
|
# Returns a humanized, readable name from image filename.
|
252
200
|
#
|
253
201
|
def humanized_name
|
254
202
|
return "" if image_file_name.blank?
|
255
203
|
|
256
|
-
convert_to_humanized_name(image_file_name,
|
204
|
+
convert_to_humanized_name(image_file_name, image_file_extension)
|
257
205
|
end
|
258
206
|
|
259
207
|
# Returns the format the image should be rendered with
|
@@ -263,9 +211,9 @@ module Alchemy
|
|
263
211
|
#
|
264
212
|
def default_render_format
|
265
213
|
if convertible?
|
266
|
-
|
214
|
+
Alchemy.config.image_output_format
|
267
215
|
else
|
268
|
-
|
216
|
+
image_file_extension
|
269
217
|
end
|
270
218
|
end
|
271
219
|
|
@@ -275,15 +223,15 @@ module Alchemy
|
|
275
223
|
# image has not a convertible file format (i.e. SVG) this returns +false+
|
276
224
|
#
|
277
225
|
def convertible?
|
278
|
-
|
279
|
-
|
226
|
+
Alchemy.config.image_output_format &&
|
227
|
+
Alchemy.config.image_output_format != "original" &&
|
280
228
|
has_convertible_format?
|
281
229
|
end
|
282
230
|
|
283
231
|
# Returns true if the image can be converted into other formats
|
284
232
|
#
|
285
233
|
def has_convertible_format?
|
286
|
-
|
234
|
+
Alchemy.storage_adapter.has_convertible_format?(self)
|
287
235
|
end
|
288
236
|
|
289
237
|
# Checks if the picture is restricted.
|
@@ -298,12 +246,32 @@ module Alchemy
|
|
298
246
|
pages.any? && pages.not_restricted.blank?
|
299
247
|
end
|
300
248
|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
249
|
+
def image_file_name
|
250
|
+
Alchemy.storage_adapter.image_file_name(self)
|
251
|
+
end
|
252
|
+
|
253
|
+
def image_file_format
|
254
|
+
Alchemy.storage_adapter.image_file_format(self)
|
255
|
+
end
|
256
|
+
|
257
|
+
def image_file_size
|
258
|
+
Alchemy.storage_adapter.image_file_size(self)
|
259
|
+
end
|
260
|
+
|
261
|
+
def image_file_width
|
262
|
+
Alchemy.storage_adapter.image_file_width(self)
|
263
|
+
end
|
264
|
+
|
265
|
+
def image_file_height
|
266
|
+
Alchemy.storage_adapter.image_file_height(self)
|
305
267
|
end
|
306
268
|
|
269
|
+
def image_file_extension
|
270
|
+
Alchemy.storage_adapter.image_file_extension(self)
|
271
|
+
end
|
272
|
+
alias_method :suffix, :image_file_extension
|
273
|
+
deprecate suffix: :image_file_extension, deprecator: Alchemy::Deprecation
|
274
|
+
|
307
275
|
# A size String from original image file values.
|
308
276
|
#
|
309
277
|
# == Example
|
@@ -314,8 +282,12 @@ module Alchemy
|
|
314
282
|
"#{image_file_width}x#{image_file_height}"
|
315
283
|
end
|
316
284
|
|
317
|
-
|
318
|
-
|
285
|
+
private
|
286
|
+
|
287
|
+
def image_file_type_allowed
|
288
|
+
unless image_file_extension&.in?(self.class.allowed_filetypes)
|
289
|
+
errors.add(:image_file, Alchemy.t("not a valid image"))
|
290
|
+
end
|
319
291
|
end
|
320
292
|
end
|
321
293
|
end
|
@@ -11,7 +11,6 @@ module Alchemy
|
|
11
11
|
extend Forwardable
|
12
12
|
|
13
13
|
include Alchemy::Logger
|
14
|
-
include Alchemy::Picture::Transformations
|
15
14
|
|
16
15
|
ANIMATED_IMAGE_FORMATS = %w[gif webp]
|
17
16
|
TRANSPARENT_IMAGE_FORMATS = %w[gif webp png]
|
@@ -89,20 +88,20 @@ module Alchemy
|
|
89
88
|
end
|
90
89
|
|
91
90
|
options = {
|
92
|
-
flatten: !render_format.in?(ANIMATED_IMAGE_FORMATS) && picture.
|
91
|
+
flatten: !render_format.in?(ANIMATED_IMAGE_FORMATS) && picture.image_file_extension == "gif"
|
93
92
|
}.with_indifferent_access.merge(options)
|
94
93
|
|
95
94
|
encoding_options = []
|
96
95
|
|
97
|
-
convert_format = render_format.sub("jpeg", "jpg") != picture.
|
96
|
+
convert_format = render_format.sub("jpeg", "jpg") != picture.image_file_extension.sub("jpeg", "jpg")
|
98
97
|
|
99
98
|
if encodable_image? && (convert_format || options[:quality])
|
100
|
-
quality = options[:quality] ||
|
99
|
+
quality = options[:quality] || Alchemy.config.output_image_quality
|
101
100
|
encoding_options << "-quality #{quality}"
|
102
101
|
end
|
103
102
|
|
104
103
|
if options[:flatten]
|
105
|
-
if render_format.in?(TRANSPARENT_IMAGE_FORMATS) && picture.
|
104
|
+
if render_format.in?(TRANSPARENT_IMAGE_FORMATS) && picture.image_file_extension.in?(TRANSPARENT_IMAGE_FORMATS)
|
106
105
|
encoding_options << "-background transparent"
|
107
106
|
end
|
108
107
|
encoding_options << "-flatten"
|
@@ -120,5 +119,116 @@ module Alchemy
|
|
120
119
|
def encodable_image?
|
121
120
|
render_format.in?(ENCODABLE_IMAGE_FORMATS)
|
122
121
|
end
|
122
|
+
|
123
|
+
# Returns the rendered cropped image. Tries to use the crop_from and crop_size
|
124
|
+
# parameters. When they can't be parsed, it just crops from the center.
|
125
|
+
#
|
126
|
+
def crop(size, crop_from = nil, crop_size = nil, upsample = false)
|
127
|
+
raise "No size given!" if size.empty?
|
128
|
+
|
129
|
+
render_to = inferred_sizes_from_string(size)
|
130
|
+
if crop_from && crop_size
|
131
|
+
top_left = point_from_string(crop_from)
|
132
|
+
crop_dimensions = inferred_sizes_from_string(crop_size)
|
133
|
+
xy_crop_resize(render_to, top_left, crop_dimensions, upsample)
|
134
|
+
else
|
135
|
+
center_crop(render_to, upsample)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns the rendered resized image using imagemagick directly.
|
140
|
+
#
|
141
|
+
def resize(size, upsample = false)
|
142
|
+
image_file.thumbnail(upsample ? size : "#{size}>")
|
143
|
+
end
|
144
|
+
|
145
|
+
# Given a string with an x, this function return a Hash with key :x and :y
|
146
|
+
#
|
147
|
+
def point_from_string(string = "0x0")
|
148
|
+
string = "0x0" if string.empty?
|
149
|
+
raise ArgumentError if !string.match(/(\d*x)|(x\d*)/)
|
150
|
+
|
151
|
+
x, y = string.scan(/(\d*)x(\d*)/)[0].map(&:to_i)
|
152
|
+
|
153
|
+
x = 0 if x.nil?
|
154
|
+
y = 0 if y.nil?
|
155
|
+
{
|
156
|
+
x: x,
|
157
|
+
y: y
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
def inferred_sizes_from_string(string)
|
162
|
+
sizes = sizes_from_string(string)
|
163
|
+
ratio = image_file_width.to_f / image_file_height
|
164
|
+
|
165
|
+
if sizes[:width].zero?
|
166
|
+
sizes[:width] = (sizes[:height] * ratio).round.to_i
|
167
|
+
end
|
168
|
+
if sizes[:height].zero?
|
169
|
+
sizes[:height] = (sizes[:width] / ratio).round.to_i
|
170
|
+
end
|
171
|
+
|
172
|
+
sizes
|
173
|
+
end
|
174
|
+
|
175
|
+
# Given a string with an x, this function returns a Hash with point
|
176
|
+
# :width and :height.
|
177
|
+
#
|
178
|
+
def sizes_from_string(string)
|
179
|
+
width, height = string.to_s.split("x", 2).map(&:to_i)
|
180
|
+
|
181
|
+
{
|
182
|
+
width: width,
|
183
|
+
height: height
|
184
|
+
}
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns true if both dimensions of the base image are bigger than the dimensions hash.
|
188
|
+
#
|
189
|
+
def is_bigger_than?(dimensions)
|
190
|
+
image_file_width > dimensions[:width] && image_file_height > dimensions[:height]
|
191
|
+
end
|
192
|
+
|
193
|
+
# Returns true is one dimension of the base image is smaller than the dimensions hash.
|
194
|
+
#
|
195
|
+
def is_smaller_than?(dimensions)
|
196
|
+
!is_bigger_than?(dimensions)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Converts a dimensions hash to a string of from "20x20"
|
200
|
+
#
|
201
|
+
def dimensions_to_string(dimensions)
|
202
|
+
"#{dimensions[:width]}x#{dimensions[:height]}"
|
203
|
+
end
|
204
|
+
|
205
|
+
# Uses imagemagick to make a centercropped thumbnail. Does not scale the image up.
|
206
|
+
#
|
207
|
+
def center_crop(dimensions, upsample)
|
208
|
+
if is_smaller_than?(dimensions) && upsample == false
|
209
|
+
dimensions = reduce_to_image(dimensions)
|
210
|
+
end
|
211
|
+
image_file.thumbnail("#{dimensions_to_string(dimensions)}#")
|
212
|
+
end
|
213
|
+
|
214
|
+
# Use imagemagick to custom crop an image. Uses -thumbnail for better performance when resizing.
|
215
|
+
#
|
216
|
+
def xy_crop_resize(dimensions, top_left, crop_dimensions, upsample)
|
217
|
+
crop_argument = dimensions_to_string(crop_dimensions)
|
218
|
+
crop_argument += "+#{top_left[:x]}+#{top_left[:y]}"
|
219
|
+
|
220
|
+
resize_argument = dimensions_to_string(dimensions)
|
221
|
+
resize_argument += ">" unless upsample
|
222
|
+
image_file.crop_resize(crop_argument, resize_argument)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Used when centercropping.
|
226
|
+
#
|
227
|
+
def reduce_to_image(dimensions)
|
228
|
+
{
|
229
|
+
width: [dimensions[:width].to_i, image_file_width.to_i].min,
|
230
|
+
height: [dimensions[:height].to_i, image_file_height.to_i].min
|
231
|
+
}
|
232
|
+
end
|
123
233
|
end
|
124
234
|
end
|
@@ -98,8 +98,10 @@ module Alchemy
|
|
98
98
|
# resource = Resource.new('/admin/tags', {"engine_name"=>"alchemy"}, Gutentag::Tag)
|
99
99
|
#
|
100
100
|
class Resource
|
101
|
+
include Alchemy::Admin::ResourceName
|
102
|
+
|
101
103
|
attr_accessor :resource_relations, :model_associations
|
102
|
-
attr_reader :model
|
104
|
+
attr_reader :model, :controller_path
|
103
105
|
|
104
106
|
DEFAULT_SKIPPED_ATTRIBUTES = %w[id created_at creator_id]
|
105
107
|
DEFAULT_SKIPPED_ASSOCIATIONS = %w[creator]
|
@@ -119,18 +121,6 @@ module Alchemy
|
|
119
121
|
end
|
120
122
|
end
|
121
123
|
|
122
|
-
def resource_array
|
123
|
-
@_resource_array ||= controller_path_array.reject { |el| el == "admin" }
|
124
|
-
end
|
125
|
-
|
126
|
-
def resources_name
|
127
|
-
@_resources_name ||= resource_array.last
|
128
|
-
end
|
129
|
-
|
130
|
-
def resource_name
|
131
|
-
@_resource_name ||= resources_name.singularize
|
132
|
-
end
|
133
|
-
|
134
124
|
def namespaced_resource_name
|
135
125
|
@_namespaced_resource_name ||= begin
|
136
126
|
namespaced_resources_name.to_s.singularize
|
@@ -296,11 +286,7 @@ module Alchemy
|
|
296
286
|
end
|
297
287
|
|
298
288
|
def guess_model_from_controller_path
|
299
|
-
|
300
|
-
end
|
301
|
-
|
302
|
-
def controller_path_array
|
303
|
-
@controller_path.split("/")
|
289
|
+
resource_model_name.classify.constantize
|
304
290
|
end
|
305
291
|
|
306
292
|
def namespace_diff
|
@@ -21,6 +21,21 @@ module Alchemy
|
|
21
21
|
searchable_alchemy_resource_associations
|
22
22
|
end
|
23
23
|
|
24
|
+
# Allow all scopes used in resource filters to be used with Ransack
|
25
|
+
def ransackable_scopes(_auth_object = nil)
|
26
|
+
if respond_to?(:alchemy_resource_filters)
|
27
|
+
alchemy_resource_filters.flat_map do |filter_set|
|
28
|
+
if respond_to?(filter_set[:name])
|
29
|
+
filter_set[:name].to_s
|
30
|
+
else
|
31
|
+
filter_set[:values].map(&:to_s)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
[]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
24
39
|
protected
|
25
40
|
|
26
41
|
def searchable_alchemy_resource_attributes
|
@@ -42,8 +42,8 @@ module Alchemy
|
|
42
42
|
#
|
43
43
|
def page_layout_names(layoutpages: false)
|
44
44
|
page_layout_definitions.select do |layout|
|
45
|
-
!!layout
|
46
|
-
end.
|
45
|
+
!!layout.layoutpage && layoutpages || !layout.layoutpage && !layoutpages
|
46
|
+
end.tap { _1.collect!(&:name) }
|
47
47
|
end
|
48
48
|
|
49
49
|
# Returns sites page layout definitions
|
@@ -52,11 +52,11 @@ module Alchemy
|
|
52
52
|
#
|
53
53
|
def page_layout_definitions
|
54
54
|
if definition["page_layouts"].presence
|
55
|
-
Alchemy::
|
56
|
-
layout
|
55
|
+
Alchemy::PageDefinition.all.select do |layout|
|
56
|
+
layout.name.in?(definition["page_layouts"])
|
57
57
|
end
|
58
58
|
else
|
59
|
-
Alchemy::
|
59
|
+
Alchemy::PageDefinition.all
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
data/app/models/alchemy/site.rb
CHANGED
@@ -21,12 +21,7 @@ module Alchemy
|
|
21
21
|
validates_uniqueness_of :host, case_sensitive: false
|
22
22
|
|
23
23
|
# associations
|
24
|
-
has_many :languages
|
25
|
-
|
26
|
-
before_destroy if: -> { languages.any? } do
|
27
|
-
errors.add(:languages, :still_present)
|
28
|
-
throw(:abort)
|
29
|
-
end
|
24
|
+
has_many :languages, dependent: :restrict_with_error
|
30
25
|
|
31
26
|
scope :published, -> { where(public: true) }
|
32
27
|
|
@@ -57,21 +52,6 @@ module Alchemy
|
|
57
52
|
end
|
58
53
|
|
59
54
|
class << self
|
60
|
-
# @deprecated Use {Alchemy::Current#site=} instead.
|
61
|
-
def current=(site)
|
62
|
-
Current.site = site
|
63
|
-
end
|
64
|
-
deprecate "current=": :"Alchemy::Current.site=", deprecator: Alchemy::Deprecation
|
65
|
-
|
66
|
-
# @deprecated Use {Alchemy::Current#site} instead.
|
67
|
-
def current
|
68
|
-
Current.site
|
69
|
-
end
|
70
|
-
deprecate current: :"Alchemy::Current.site", deprecator: Alchemy::Deprecation
|
71
|
-
|
72
|
-
alias_method :default, :first
|
73
|
-
deprecate default: :first, deprecator: Alchemy::Deprecation
|
74
|
-
|
75
55
|
def find_for_host(host)
|
76
56
|
# These are split up into two separate queries in order to run the
|
77
57
|
# fastest query first (selecting the domain by its primary host name).
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Alchemy
|
4
|
+
class StorageAdapter
|
5
|
+
# The class representing an URL to an ActiveStorage attachment
|
6
|
+
#
|
7
|
+
class ActiveStorage::AttachmentUrl
|
8
|
+
attr_reader :attachment
|
9
|
+
|
10
|
+
def initialize(attachment)
|
11
|
+
@attachment = attachment
|
12
|
+
end
|
13
|
+
|
14
|
+
# The attachment url
|
15
|
+
#
|
16
|
+
# @param [Hash] options
|
17
|
+
# @option options [Symbol] :download return a URL for downloading the attachment
|
18
|
+
# @option options [Symbol] :name The filename
|
19
|
+
# @option options [Symbol] :format The file extension
|
20
|
+
#
|
21
|
+
# @return [String]
|
22
|
+
#
|
23
|
+
def call(options = {})
|
24
|
+
if attachment.file.attached?
|
25
|
+
options[:format] ||= attachment.suffix
|
26
|
+
if options.delete(:download)
|
27
|
+
routes.download_attachment_path(attachment, options)
|
28
|
+
else
|
29
|
+
routes.show_attachment_path(attachment, options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def routes
|
37
|
+
Alchemy::Engine.routes.url_helpers
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Alchemy
|
4
|
+
class StorageAdapter
|
5
|
+
# Returns the URL to a variant of a picture using ActiveStorage
|
6
|
+
class ActiveStorage::PictureUrl
|
7
|
+
attr_reader :picture, :image_file
|
8
|
+
|
9
|
+
# @param [Alchemy::Picture]
|
10
|
+
#
|
11
|
+
def initialize(picture)
|
12
|
+
@picture = picture
|
13
|
+
@image_file = picture.image_file
|
14
|
+
end
|
15
|
+
|
16
|
+
# The URL to a variant of a picture
|
17
|
+
#
|
18
|
+
# @return [String]
|
19
|
+
#
|
20
|
+
def call(options = {})
|
21
|
+
variant_options = DragonflyToImageProcessing.call(options)
|
22
|
+
variant_options[:format] = options[:format] || default_output_format
|
23
|
+
variant = image_file&.variant(variant_options)
|
24
|
+
return unless variant
|
25
|
+
|
26
|
+
Rails.application.routes.url_helpers.rails_blob_path(
|
27
|
+
variant,
|
28
|
+
{
|
29
|
+
filename: filename(options),
|
30
|
+
format: variant_options[:format],
|
31
|
+
only_path: true
|
32
|
+
}
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def filename(options = {})
|
39
|
+
if picture.name.presence
|
40
|
+
picture.name.to_param
|
41
|
+
else
|
42
|
+
picture.image_file_name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def default_output_format
|
47
|
+
if Alchemy.config.image_output_format == "original"
|
48
|
+
picture.image_file_extension
|
49
|
+
else
|
50
|
+
Alchemy.config.image_output_format
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|