alchemy_cms 7.4.16 → 8.0.0.a
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/CHANGELOG.md +87 -77
- data/Gemfile +13 -7
- data/README.md +13 -5
- data/alchemy_cms.gemspec +14 -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/custom-properties.css +1 -1
- 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/ui/alchemy/content.min.css +1 -0
- data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css +1 -1
- data/app/assets/config/alchemy_manifest.js +0 -2
- data/app/assets/images/alchemy/icons-sprite.svg +1 -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 +6 -3
- data/app/components/alchemy/ingredients/datetime_view.rb +2 -4
- data/app/components/alchemy/ingredients/number_view.rb +18 -0
- data/app/controllers/alchemy/admin/attachments_controller.rb +8 -15
- data/app/controllers/alchemy/admin/clipboard_controller.rb +2 -6
- data/app/controllers/alchemy/admin/elements_controller.rb +1 -1
- data/app/controllers/alchemy/admin/languages_controller.rb +1 -4
- data/app/controllers/alchemy/admin/pages_controller.rb +15 -17
- data/app/controllers/alchemy/admin/pictures_controller.rb +9 -5
- data/app/controllers/alchemy/admin/resources_controller.rb +16 -106
- data/app/controllers/alchemy/api/nodes_controller.rb +1 -1
- data/app/controllers/alchemy/api/pages_controller.rb +0 -2
- 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 +7 -2
- data/app/controllers/concerns/alchemy/admin/resource_filter.rb +92 -0
- data/app/decorators/alchemy/element_editor.rb +5 -48
- data/app/decorators/alchemy/ingredient_editor.rb +4 -62
- data/app/helpers/alchemy/admin/base_helper.rb +14 -84
- data/app/helpers/alchemy/admin/elements_helper.rb +4 -4
- 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 +2 -2
- data/{lib → app/helpers}/alchemy/resources_helper.rb +6 -46
- 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/datepicker.js +10 -2
- data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +7 -7
- data/app/javascript/alchemy_admin/components/element_editor.js +1 -1
- data/app/javascript/alchemy_admin/components/index.js +1 -3
- data/app/javascript/alchemy_admin/components/remote_select.js +4 -1
- data/app/javascript/alchemy_admin/components/tags_autocomplete.js +5 -1
- data/app/javascript/alchemy_admin/components/tinymce.js +4 -2
- 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/dirty.js +3 -2
- data/app/javascript/alchemy_admin/i18n.js +15 -16
- data/app/javascript/alchemy_admin/initializer.js +1 -49
- data/app/javascript/alchemy_admin/link_dialog.js +1 -6
- data/app/javascript/alchemy_admin/utils/ajax.js +51 -44
- data/app/javascript/alchemy_admin.js +3 -8
- 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 -37
- 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 +9 -27
- data/app/models/alchemy/element_definition.rb +160 -0
- data/app/models/alchemy/ingredient.rb +11 -44
- 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/code.rb +0 -1
- data/app/models/alchemy/language.rb +0 -14
- data/app/models/alchemy/message.rb +3 -7
- data/app/models/alchemy/node.rb +1 -22
- 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 +0 -1
- data/app/models/alchemy/page/page_natures.rb +7 -7
- data/app/models/alchemy/page/page_scopes.rb +1 -1
- data/app/models/alchemy/page.rb +11 -33
- data/app/models/alchemy/page_definition.rb +115 -0
- data/app/models/alchemy/picture.rb +71 -88
- 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 -6
- data/app/models/alchemy/site.rb +0 -15
- 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/{picture/url.rb → storage_adapter/dragonfly/picture_url.rb} +28 -12
- data/app/models/alchemy/{picture → storage_adapter/dragonfly}/preprocessor.rb +4 -4
- data/app/models/alchemy/storage_adapter/dragonfly.rb +183 -0
- data/app/models/alchemy/storage_adapter.rb +74 -0
- data/app/models/concerns/alchemy/picture_thumbnails.rb +19 -6
- data/app/serializers/alchemy/element_serializer.rb +0 -1
- data/app/serializers/alchemy/page_tree_serializer.rb +1 -17
- data/app/services/alchemy/dragonfly_to_image_processing.rb +100 -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 +33 -49
- data/app/stylesheets/alchemy/_variables.scss +5 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/archive.scss +20 -37
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/base.scss +16 -14
- data/app/stylesheets/alchemy/admin/buttons.scss +160 -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 +23 -16
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/elements.scss +150 -105
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/errors.scss +5 -5
- data/app/stylesheets/alchemy/admin/filters.scss +58 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/flatpickr.scss +53 -60
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/form_fields.scss +21 -7
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/forms.scss +31 -19
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/frame.scss +20 -16
- data/app/stylesheets/alchemy/admin/hints.scss +5 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/icons.scss +10 -1
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/image_library.scss +10 -8
- 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 +61 -55
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/nodes.scss +21 -18
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/notices.scss +18 -18
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/page-select.scss +2 -2
- data/app/stylesheets/alchemy/admin/pagination.scss +144 -0
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/preview_window.scss +8 -6
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/print.scss +1 -1
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/resource_info.scss +8 -5
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/search.scss +9 -6
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/selects.scss +49 -37
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/shoelace.scss +5 -6
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/sitemap.scss +38 -33
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tables.scss +6 -4
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/tags.scss +6 -4
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/toolbar.scss +12 -6
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/typography.scss +2 -2
- data/app/{assets/stylesheets → stylesheets}/alchemy/admin/upload.scss +7 -5
- data/app/stylesheets/alchemy/admin.scss +44 -0
- data/app/stylesheets/alchemy/custom-properties.css +244 -0
- data/app/stylesheets/alchemy/welcome.scss +75 -0
- data/app/{assets/stylesheets → stylesheets}/tinymce/skins/content/alchemy/content.scss +8 -9
- data/app/stylesheets/tinymce/skins/ui/alchemy/content.scss +1 -0
- data/app/{assets/stylesheets → stylesheets}/tinymce/skins/ui/alchemy/skin.scss +133 -136
- data/app/views/alchemy/admin/attachments/_files_list.html.erb +2 -2
- data/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb +1 -1
- 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/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 +1 -1
- data/app/views/alchemy/admin/elements/_header.html.erb +1 -0
- 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 +1 -10
- 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/pages/info.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 -10
- data/app/views/alchemy/admin/pictures/_archive.html.erb +4 -7
- data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +2 -1
- 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/index.html.erb +2 -7
- data/app/views/alchemy/admin/resources/_applied_filters.html.erb +8 -0
- data/app/views/alchemy/admin/resources/_filter_bar.html.erb +11 -21
- 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/_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 +8 -0
- 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/ingredients/_number_editor.html.erb +24 -0
- data/app/views/alchemy/ingredients/_page_editor.html.erb +1 -0
- data/app/views/alchemy/ingredients/_richtext_editor.html.erb +1 -0
- data/app/views/alchemy/ingredients/_select_editor.html.erb +2 -1
- 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 +5 -9
- data/bun.lockb +0 -0
- data/bundles/remixicon.mjs +153 -0
- 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 +32 -14
- data/config/routes.rb +0 -2
- data/eslint.config.js +2 -1
- 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 +216 -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/controller_actions.rb +1 -2
- data/lib/alchemy/engine.rb +65 -17
- data/lib/alchemy/hints.rb +3 -7
- data/lib/alchemy/name_conversions.rb +0 -6
- data/lib/alchemy/on_page_layout.rb +2 -2
- data/lib/alchemy/permissions.rb +0 -10
- data/lib/alchemy/propshaft/tinymce_asset.rb +15 -0
- data/lib/alchemy/seeder.rb +6 -12
- 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 +5 -9
- data/lib/alchemy/test_support/factories/page_factory.rb +2 -4
- data/lib/alchemy/test_support/factories/picture_factory.rb +30 -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/shared_ingredient_examples.rb +5 -5
- data/lib/alchemy/upgrader/.keep +0 -0
- 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 +192 -170
- 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 +5 -14
- data/lib/generators/alchemy/install/templates/alchemy.rb.tt +196 -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/upgrade.rake +12 -47
- data/lib/tasks/alchemy/usage.rake +0 -2
- data/vendor/javascript/tinymce.min.js +1 -1
- data/vitest.config.js +21 -0
- metadata +184 -183
- 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.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/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.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/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/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/config/initializers/assets.rb +0 -4
- 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/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/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/attachments.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/node-select.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
|
@@ -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
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
module Alchemy
|
|
4
4
|
module Site::Layout
|
|
5
5
|
extend ActiveSupport::Concern
|
|
6
|
-
|
|
7
6
|
SITE_DEFINITIONS_FILE = Rails.root.join("config/alchemy/site_layouts.yml")
|
|
8
7
|
|
|
9
8
|
module ClassMethods
|
|
@@ -42,8 +41,8 @@ module Alchemy
|
|
|
42
41
|
#
|
|
43
42
|
def page_layout_names(layoutpages: false)
|
|
44
43
|
page_layout_definitions.select do |layout|
|
|
45
|
-
!!layout
|
|
46
|
-
end.
|
|
44
|
+
!!layout.layoutpage && layoutpages || !layout.layoutpage && !layoutpages
|
|
45
|
+
end.tap { _1.collect!(&:name) }
|
|
47
46
|
end
|
|
48
47
|
|
|
49
48
|
# Returns sites page layout definitions
|
|
@@ -52,11 +51,11 @@ module Alchemy
|
|
|
52
51
|
#
|
|
53
52
|
def page_layout_definitions
|
|
54
53
|
if definition["page_layouts"].presence
|
|
55
|
-
Alchemy::
|
|
56
|
-
layout
|
|
54
|
+
Alchemy::PageDefinition.all.select do |layout|
|
|
55
|
+
layout.name.in?(definition["page_layouts"])
|
|
57
56
|
end
|
|
58
57
|
else
|
|
59
|
-
Alchemy::
|
|
58
|
+
Alchemy::PageDefinition.all
|
|
60
59
|
end
|
|
61
60
|
end
|
|
62
61
|
|
data/app/models/alchemy/site.rb
CHANGED
|
@@ -57,21 +57,6 @@ module Alchemy
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
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
60
|
def find_for_host(host)
|
|
76
61
|
# These are split up into two separate queries in order to run the
|
|
77
62
|
# 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
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemy
|
|
4
|
+
class StorageAdapter
|
|
5
|
+
class ActiveStorage::Preprocessor
|
|
6
|
+
def initialize(attachable)
|
|
7
|
+
@attachable = attachable
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Preprocess images after upload
|
|
11
|
+
#
|
|
12
|
+
# Define preprocessing options in the Alchemy.config
|
|
13
|
+
#
|
|
14
|
+
# preprocess_image_resize [String] - Downsizing example: '1000x1000>'
|
|
15
|
+
#
|
|
16
|
+
def call
|
|
17
|
+
max_image_size = Alchemy.config.get(:preprocess_image_resize)
|
|
18
|
+
if max_image_size.present?
|
|
19
|
+
self.class.process_thumb(attachable, size: max_image_size)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attr_reader :attachable
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
def generate_thumbs!(attachable)
|
|
27
|
+
Alchemy::Picture::THUMBNAIL_SIZES.values.each do |size|
|
|
28
|
+
process_thumb(attachable, size: size, flatten: true)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def process_thumb(attachable, options = {})
|
|
33
|
+
attachable.variant :thumb,
|
|
34
|
+
**Alchemy::DragonflyToImageProcessing.call(options),
|
|
35
|
+
preprocessed: true
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
module Alchemy
|
|
2
|
+
class StorageAdapter
|
|
3
|
+
module ActiveStorage
|
|
4
|
+
module PictureClassMethods
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.has_one_attached :image_file do |attachable|
|
|
7
|
+
# Only works in Rails 7.1+
|
|
8
|
+
# https://github.com/rails/rails/pull/47473
|
|
9
|
+
Alchemy.storage_adapter.preprocessor_class.new(attachable).call
|
|
10
|
+
Alchemy.storage_adapter.preprocessor_class.generate_thumbs!(attachable)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module AttachmentClassMethods
|
|
16
|
+
def self.included(base)
|
|
17
|
+
base.has_one_attached :file
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
extend self
|
|
22
|
+
|
|
23
|
+
def attachment_url_class
|
|
24
|
+
AttachmentUrl
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def preprocessor_class
|
|
28
|
+
Preprocessor
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def picture_url_class
|
|
32
|
+
PictureUrl
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def file_formats(class_name, scope:)
|
|
36
|
+
attachment_scope = case class_name
|
|
37
|
+
when "Alchemy::Attachment" then scope.with_attached_file
|
|
38
|
+
when "Alchemy::Picture" then scope.with_attached_image_file
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
attachment_scope.pluck("active_storage_blobs.content_type").uniq.tap(&:compact!).presence || []
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @param [String]
|
|
45
|
+
# @return [Array<String>]
|
|
46
|
+
def searchable_alchemy_resource_attributes(class_name)
|
|
47
|
+
case class_name
|
|
48
|
+
when "Alchemy::Attachment"
|
|
49
|
+
%w[name file_blob_filename]
|
|
50
|
+
when "Alchemy::Picture"
|
|
51
|
+
%w[name image_file_blob_filename]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @param [String]
|
|
56
|
+
# @return [Array<String>]
|
|
57
|
+
def ransackable_attributes(_class_name)
|
|
58
|
+
%w[name]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @param [String]
|
|
62
|
+
# @return [Array<String>]
|
|
63
|
+
def ransackable_associations(class_name)
|
|
64
|
+
case class_name
|
|
65
|
+
when "Alchemy::Attachment"
|
|
66
|
+
%w[file_blob]
|
|
67
|
+
when "Alchemy::Picture"
|
|
68
|
+
%w[image_file_blob]
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def rescuable_errors
|
|
73
|
+
::ActiveStorage::Error
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @param [String]
|
|
77
|
+
# @return [Alchemy::Picture::ActiveRecord_Relation]
|
|
78
|
+
def by_file_format_scope(file_format)
|
|
79
|
+
Picture.with_attached_image_file.joins(:image_file_blob).where(active_storage_blobs: {content_type: file_format})
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @param [String]
|
|
83
|
+
# @return [Alchemy::Atachment::ActiveRecord_Relation]
|
|
84
|
+
def by_file_type_scope(file_type)
|
|
85
|
+
Attachment.with_attached_file.joins(:file_blob).where(active_storage_blobs: {content_type: file_type})
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @param [Alchemy::Attachment]
|
|
89
|
+
# @return [String]
|
|
90
|
+
def file_name(attachment)
|
|
91
|
+
attachment.file&.filename&.to_s
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @param [Alchemy::Attachment]
|
|
95
|
+
# @return [Integer]
|
|
96
|
+
def file_size(attachment)
|
|
97
|
+
attachment.file&.byte_size
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @param [Alchemy::Attachment]
|
|
101
|
+
# @return [String]
|
|
102
|
+
def file_mime_type(attachment)
|
|
103
|
+
attachment.file&.content_type
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @param [Alchemy::Attachment]
|
|
107
|
+
# @return [String]
|
|
108
|
+
def file_extension(attachment)
|
|
109
|
+
attachment.file&.filename&.extension
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @param [Alchemy::Picture]
|
|
113
|
+
# @return [TrueClass, FalseClass]
|
|
114
|
+
def has_convertible_format?(picture)
|
|
115
|
+
picture.image_file&.variable?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @param [Alchemy::Picture]
|
|
119
|
+
# @return [String]
|
|
120
|
+
def image_file_name(picture)
|
|
121
|
+
picture.image_file&.filename&.to_s
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @param [Alchemy::Picture]
|
|
125
|
+
# @return [String]
|
|
126
|
+
def image_file_format(picture)
|
|
127
|
+
picture.image_file&.content_type
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# @param [Alchemy::Picture]
|
|
131
|
+
# @return [Integer]
|
|
132
|
+
def image_file_size(picture)
|
|
133
|
+
picture.image_file&.byte_size
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @param [Alchemy::Picture]
|
|
137
|
+
# @return [Integer]
|
|
138
|
+
def image_file_width(picture)
|
|
139
|
+
picture.image_file&.metadata&.fetch(:width, nil)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @param [Alchemy::Picture]
|
|
143
|
+
# @return [Integer]
|
|
144
|
+
def image_file_height(picture)
|
|
145
|
+
picture.image_file&.metadata&.fetch(:height, nil)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# @param [Alchemy::Picture]
|
|
149
|
+
# @return [Integer]
|
|
150
|
+
def image_file_extension(picture)
|
|
151
|
+
picture.image_file&.filename&.extension&.downcase
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @param [Alchemy::Picture]
|
|
155
|
+
# @return [TrueClass, FalseClass]
|
|
156
|
+
def image_file_present?(picture)
|
|
157
|
+
picture.image_file.attached?
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# @param Alchemy::Picture::ActiveRecord_Relation
|
|
161
|
+
# @return Alchemy::Picture::ActiveRecord_Relation
|
|
162
|
+
def preloaded_pictures(pictures)
|
|
163
|
+
pictures.with_attached_image_file
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @param [Alchemy::Attachment]
|
|
167
|
+
# @return [TrueClass, FalseClass]
|
|
168
|
+
def set_attachment_name?(attachment)
|
|
169
|
+
attachment.file.changed?
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Alchemy
|
|
4
|
-
class
|
|
5
|
-
# The class representing an URL to an attachment
|
|
4
|
+
class StorageAdapter
|
|
5
|
+
# The class representing an URL to an Dragonfly attachment
|
|
6
6
|
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
#
|
|
11
|
-
class Url
|
|
7
|
+
class Dragonfly::AttachmentUrl
|
|
8
|
+
attr_reader :attachment
|
|
9
|
+
|
|
12
10
|
def initialize(attachment)
|
|
13
11
|
@attachment = attachment
|
|
14
12
|
end
|
|
@@ -23,11 +21,13 @@ module Alchemy
|
|
|
23
21
|
# @return [String]
|
|
24
22
|
#
|
|
25
23
|
def call(options = {})
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
if attachment.file
|
|
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
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -1,23 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Alchemy
|
|
4
|
-
class
|
|
5
|
-
class
|
|
6
|
-
|
|
4
|
+
class StorageAdapter
|
|
5
|
+
class Dragonfly::PictureUrl
|
|
6
|
+
TRANSFORMATION_OPTIONS = [
|
|
7
|
+
:crop,
|
|
8
|
+
:crop_from,
|
|
9
|
+
:crop_size,
|
|
10
|
+
:flatten,
|
|
11
|
+
:format,
|
|
12
|
+
:quality,
|
|
13
|
+
:size,
|
|
14
|
+
:upsample
|
|
15
|
+
]
|
|
7
16
|
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
def initialize(variant)
|
|
11
|
-
raise ArgumentError, "Variant missing!" if variant.nil?
|
|
17
|
+
attr_reader :picture, :variant, :thumb
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
# @param [Alchemy::Picture]
|
|
20
|
+
#
|
|
21
|
+
def initialize(picture)
|
|
22
|
+
@picture = picture
|
|
14
23
|
end
|
|
15
24
|
|
|
16
25
|
# The URL to a variant of a picture
|
|
17
26
|
#
|
|
18
27
|
# @return [String]
|
|
19
28
|
#
|
|
20
|
-
def call(
|
|
29
|
+
def call(options = {})
|
|
30
|
+
@variant = PictureVariant.new(picture, options.slice(*TRANSFORMATION_OPTIONS))
|
|
31
|
+
params = {
|
|
32
|
+
basename: picture.name,
|
|
33
|
+
ext: variant.render_format,
|
|
34
|
+
name: picture.name
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
return variant.image.url(params) unless processible_image?
|
|
22
38
|
|
|
23
39
|
"/#{uid}"
|
|
@@ -43,10 +59,10 @@ module Alchemy
|
|
|
43
59
|
end
|
|
44
60
|
|
|
45
61
|
def find_thumb_by(signature)
|
|
46
|
-
@thumb = if
|
|
47
|
-
|
|
62
|
+
@thumb = if picture.thumbs.loaded?
|
|
63
|
+
picture.thumbs.find { |t| t.signature == signature }
|
|
48
64
|
else
|
|
49
|
-
|
|
65
|
+
picture.thumbs.find_by(signature: signature)
|
|
50
66
|
end
|
|
51
67
|
end
|
|
52
68
|
end
|