alchemy_cms 7.4.5 → 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.
Potentially problematic release.
This version of alchemy_cms might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +92 -0
- data/Gemfile +13 -6
- 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 +5 -2
- 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 -1
- data/app/controllers/alchemy/admin/pages_controller.rb +15 -15
- data/app/controllers/alchemy/admin/picture_descriptions_controller.rb +5 -1
- data/app/controllers/alchemy/admin/pictures_controller.rb +10 -5
- 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 +7 -2
- data/app/controllers/concerns/alchemy/admin/picture_descriptions_form_helper.rb +16 -0
- 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 +3 -53
- 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 +1 -1
- data/{lib → app/helpers}/alchemy/resources_helper.rb +5 -45
- data/app/javascript/alchemy_admin/components/action.js +4 -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/growl.js +1 -0
- data/app/javascript/alchemy_admin/components/index.js +1 -0
- 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/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 +51 -34
- 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 +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 +0 -14
- 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_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 +69 -79
- 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 +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/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 +36 -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 +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/picture_descriptions/_form.html.erb +5 -7
- data/app/views/alchemy/admin/picture_descriptions/edit.html.erb +2 -2
- 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 +2 -2
- data/app/views/alchemy/admin/pictures/_picture_description_field.html.erb +21 -13
- 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/base/500.turbo_stream.erb +3 -0
- data/app/views/alchemy/base/redirect.js.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 +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/db/migrate/20250626160259_add_unique_index_to_picture_descriptions.rb +7 -0
- 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/engine.rb +65 -17
- data/lib/alchemy/hints.rb +3 -7
- 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/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 +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/vendor/javascript/tinymce.min.js +1 -1
- data/vitest.config.js +21 -0
- metadata +184 -180
- 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 -124
- 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
data/app/models/alchemy/page.rb
CHANGED
@@ -36,7 +36,7 @@
|
|
36
36
|
#
|
37
37
|
|
38
38
|
require_dependency "alchemy/page/fixed_attributes"
|
39
|
-
require_dependency "alchemy/page/
|
39
|
+
require_dependency "alchemy/page/definitions"
|
40
40
|
require_dependency "alchemy/page/page_scopes"
|
41
41
|
require_dependency "alchemy/page/page_natures"
|
42
42
|
require_dependency "alchemy/page/page_naming"
|
@@ -44,7 +44,6 @@ require_dependency "alchemy/page/page_elements"
|
|
44
44
|
|
45
45
|
module Alchemy
|
46
46
|
class Page < BaseRecord
|
47
|
-
include Alchemy::Hints
|
48
47
|
include Alchemy::Logger
|
49
48
|
include Alchemy::Taggable
|
50
49
|
|
@@ -90,7 +89,7 @@ module Alchemy
|
|
90
89
|
|
91
90
|
acts_as_nested_set(dependent: :destroy, scope: [:layoutpage, :language_id])
|
92
91
|
|
93
|
-
stampable stamper_class_name: Alchemy.
|
92
|
+
stampable stamper_class_name: Alchemy.user_class_name
|
94
93
|
|
95
94
|
belongs_to :language
|
96
95
|
|
@@ -156,7 +155,7 @@ module Alchemy
|
|
156
155
|
after_update :touch_nodes
|
157
156
|
|
158
157
|
# Concerns
|
159
|
-
include
|
158
|
+
include Definitions
|
160
159
|
include PageScopes
|
161
160
|
include PageNatures
|
162
161
|
include PageNaming
|
@@ -164,6 +163,7 @@ module Alchemy
|
|
164
163
|
|
165
164
|
# site_name accessor
|
166
165
|
delegate :name, to: :site, prefix: true, allow_nil: true
|
166
|
+
delegate :has_hint?, :hint, to: :definition
|
167
167
|
|
168
168
|
# Class methods
|
169
169
|
#
|
@@ -183,37 +183,10 @@ module Alchemy
|
|
183
183
|
@_url_path_class = klass
|
184
184
|
end
|
185
185
|
|
186
|
-
def alchemy_resource_filters
|
187
|
-
[
|
188
|
-
{
|
189
|
-
name: :by_page_layout,
|
190
|
-
values: PageLayout.all.map { |p| [Alchemy.t(p["name"], scope: "page_layout_names"), p["name"]] }
|
191
|
-
},
|
192
|
-
{
|
193
|
-
name: :status,
|
194
|
-
values: %w[published not_public restricted]
|
195
|
-
}
|
196
|
-
]
|
197
|
-
end
|
198
|
-
|
199
186
|
def searchable_alchemy_resource_attributes
|
200
187
|
%w[name urlname title]
|
201
188
|
end
|
202
189
|
|
203
|
-
# Used to store the current page previewed in the edit page template.
|
204
|
-
# @deprecated Use {Alchemy::Current#preview_page=} instead.
|
205
|
-
def current_preview=(page)
|
206
|
-
Current.preview_page = page
|
207
|
-
end
|
208
|
-
deprecate "current_preview=": :"Alchemy::Current.preview_page=", deprecator: Alchemy::Deprecation
|
209
|
-
|
210
|
-
# Returns the current page previewed in the edit page template.
|
211
|
-
# @deprecated Use {Alchemy::Current#preview_page} instead.
|
212
|
-
def current_preview
|
213
|
-
Current.preview_page
|
214
|
-
end
|
215
|
-
deprecate current_preview: :"Alchemy::Current.preview_page", deprecator: Alchemy::Deprecation
|
216
|
-
|
217
190
|
# @return the language root page for given language id.
|
218
191
|
# @param language_id [Fixnum]
|
219
192
|
#
|
@@ -265,13 +238,13 @@ module Alchemy
|
|
265
238
|
|
266
239
|
clipboard_pages = all_from_clipboard(clipboard)
|
267
240
|
allowed_page_layouts = Alchemy::Page.selectable_layouts(language_id, layoutpages: layoutpages)
|
268
|
-
allowed_page_layout_names = allowed_page_layouts.collect
|
241
|
+
allowed_page_layout_names = allowed_page_layouts.collect(&:name)
|
269
242
|
clipboard_pages.select { |cp| allowed_page_layout_names.include?(cp.page_layout) }
|
270
243
|
end
|
271
244
|
|
272
245
|
def link_target_options
|
273
246
|
options = [[Alchemy.t(:default, scope: "link_target_options"), ""]]
|
274
|
-
link_target_options =
|
247
|
+
link_target_options = Alchemy.config.link_target_options
|
275
248
|
link_target_options.each do |option|
|
276
249
|
# add an underscore to the options to provide the default syntax
|
277
250
|
# @link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target
|
@@ -280,6 +253,11 @@ module Alchemy
|
|
280
253
|
end
|
281
254
|
options
|
282
255
|
end
|
256
|
+
|
257
|
+
# Allow all string and text attributes to be searchable by Ransack.
|
258
|
+
def ransackable_attributes(_auth_object = nil)
|
259
|
+
searchable_alchemy_resource_attributes + ["updated_at"]
|
260
|
+
end
|
283
261
|
end
|
284
262
|
|
285
263
|
# Instance methods
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Alchemy
|
4
|
+
class PageDefinition
|
5
|
+
include ActiveModel::Model
|
6
|
+
include ActiveModel::Attributes
|
7
|
+
include Alchemy::Hints
|
8
|
+
|
9
|
+
extend ActiveModel::Translation
|
10
|
+
|
11
|
+
attribute :name, :string
|
12
|
+
attribute :elements, default: []
|
13
|
+
attribute :autogenerate, default: []
|
14
|
+
attribute :layoutpage, :boolean, default: false
|
15
|
+
attribute :unique, :boolean, default: false
|
16
|
+
attribute :cache, :boolean, default: true
|
17
|
+
attribute :insert_elements_at, :string, default: "bottom"
|
18
|
+
attribute :fixed_attributes, default: {}
|
19
|
+
attribute :searchable, :boolean, default: true
|
20
|
+
attribute :searchresults, :boolean, default: false
|
21
|
+
attribute :hide, :boolean, default: false
|
22
|
+
attribute :editable_by
|
23
|
+
attribute :hint
|
24
|
+
|
25
|
+
validates :name,
|
26
|
+
presence: true,
|
27
|
+
format: {
|
28
|
+
with: /\A[a-z_-]+\z/
|
29
|
+
}
|
30
|
+
|
31
|
+
delegate :[], to: :attributes
|
32
|
+
delegate :blank?, to: :name
|
33
|
+
|
34
|
+
class << self
|
35
|
+
# Returns all page layouts.
|
36
|
+
#
|
37
|
+
# They are defined in +config/alchemy/page_layout.yml+ file.
|
38
|
+
#
|
39
|
+
def all
|
40
|
+
@definitions ||= read_definitions_file.map { new(**_1) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def map(...)
|
44
|
+
all.map(...)
|
45
|
+
end
|
46
|
+
alias_method :collect, :map
|
47
|
+
|
48
|
+
# Add additional page definitions to collection.
|
49
|
+
#
|
50
|
+
# Useful for extending the page layouts from an Alchemy module.
|
51
|
+
#
|
52
|
+
# === Usage Example
|
53
|
+
#
|
54
|
+
# Call +Alchemy::PageDefinition.add(your_definition)+ in your engine.rb file.
|
55
|
+
#
|
56
|
+
# @param [Array || Hash]
|
57
|
+
# You can pass a single layout definition as Hash, or a collection of page layouts as Array.
|
58
|
+
#
|
59
|
+
def add(definition)
|
60
|
+
all
|
61
|
+
@definitions += Array.wrap(definition).map { new(**_1) }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns one page definition by given name.
|
65
|
+
#
|
66
|
+
def get(name)
|
67
|
+
return new if name.blank?
|
68
|
+
|
69
|
+
all.detect { _1.name.casecmp(name).zero? }
|
70
|
+
end
|
71
|
+
|
72
|
+
def reset!
|
73
|
+
@definitions = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
# The absolute +page_layouts.yml+ file path
|
77
|
+
# @return [Pathname]
|
78
|
+
def layouts_file_path
|
79
|
+
Rails.root.join("config", "alchemy", "page_layouts.yml")
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Reads the layout definitions from +config/alchemy/page_layouts.yml+.
|
85
|
+
#
|
86
|
+
def read_definitions_file
|
87
|
+
if File.exist?(layouts_file_path)
|
88
|
+
Array.wrap(
|
89
|
+
YAML.safe_load(
|
90
|
+
ERB.new(File.read(layouts_file_path)).result,
|
91
|
+
permitted_classes: YAML_PERMITTED_CLASSES,
|
92
|
+
aliases: true
|
93
|
+
) || []
|
94
|
+
)
|
95
|
+
else
|
96
|
+
raise LoadError, "Could not find page_layouts.yml file! Please run `rails generate alchemy:install`"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def human_name
|
102
|
+
Alchemy::Page.human_layout_name(name)
|
103
|
+
end
|
104
|
+
|
105
|
+
def attributes
|
106
|
+
super.with_indifferent_access
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def hint_translation_scope
|
112
|
+
:page_hints
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -28,24 +28,10 @@ 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 Calculations
|
49
35
|
|
50
36
|
has_many :picture_ingredients,
|
51
37
|
class_name: "Alchemy::Ingredients::Picture",
|
@@ -54,7 +40,6 @@ module Alchemy
|
|
54
40
|
|
55
41
|
has_many :elements, through: :picture_ingredients
|
56
42
|
has_many :pages, through: :elements
|
57
|
-
has_many :thumbs, class_name: "Alchemy::PictureThumb", dependent: :destroy
|
58
43
|
has_many :descriptions, class_name: "Alchemy::PictureDescription", dependent: :destroy
|
59
44
|
|
60
45
|
accepts_nested_attributes_for :descriptions, allow_destroy: true, reject_if: ->(attr) { attr[:text].blank? }
|
@@ -72,7 +57,7 @@ module Alchemy
|
|
72
57
|
|
73
58
|
# Image preprocessing class
|
74
59
|
def self.preprocessor_class
|
75
|
-
@_preprocessor_class ||=
|
60
|
+
@_preprocessor_class ||= Alchemy.storage_adapter.preprocessor_class
|
76
61
|
end
|
77
62
|
|
78
63
|
# Set a image preprocessing class
|
@@ -84,35 +69,20 @@ module Alchemy
|
|
84
69
|
@_preprocessor_class = klass
|
85
70
|
end
|
86
71
|
|
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
|
-
# Create important thumbnails upfront
|
98
|
-
after_create -> { PictureThumb.generate_thumbs!(self) if has_convertible_format? }
|
72
|
+
include Alchemy.storage_adapter.picture_class_methods
|
99
73
|
|
100
74
|
# We need to define this method here to have it available in the validations below.
|
101
75
|
class << self
|
102
76
|
def allowed_filetypes
|
103
|
-
|
77
|
+
Alchemy.config.uploader.allowed_filetypes.alchemy_pictures
|
104
78
|
end
|
105
79
|
end
|
106
80
|
|
107
81
|
validates_presence_of :image_file
|
108
|
-
validates_size_of :image_file, maximum:
|
109
|
-
|
110
|
-
of: :image_file,
|
111
|
-
in: allowed_filetypes,
|
112
|
-
case_sensitive: false,
|
113
|
-
message: Alchemy.t("not a valid image")
|
82
|
+
validates_size_of :image_file, maximum: Alchemy.config.uploader.file_size_limit.megabytes
|
83
|
+
validate :image_file_type_allowed, if: -> { image_file.present? }
|
114
84
|
|
115
|
-
stampable stamper_class_name: Alchemy.
|
85
|
+
stampable stamper_class_name: Alchemy.user_class_name
|
116
86
|
|
117
87
|
scope :named, ->(name) { where("#{table_name}.name LIKE ?", "%#{name}%") }
|
118
88
|
scope :recent, -> { where("#{table_name}.created_at > ?", Time.current - 24.hours).order(:created_at) }
|
@@ -121,16 +91,17 @@ module Alchemy
|
|
121
91
|
where("#{table_name}.id NOT IN (SELECT related_object_id FROM alchemy_ingredients WHERE related_object_type = 'Alchemy::Picture')")
|
122
92
|
}
|
123
93
|
scope :without_tag, -> { left_outer_joins(:taggings).where(gutentag_taggings: {id: nil}) }
|
124
|
-
scope :by_file_format, ->(
|
94
|
+
scope :by_file_format, ->(file_format) do
|
95
|
+
Alchemy.storage_adapter.by_file_format_scope(file_format)
|
96
|
+
end
|
125
97
|
|
126
98
|
# Class methods
|
127
99
|
|
128
100
|
class << self
|
129
101
|
# The class used to generate URLs for pictures
|
130
102
|
#
|
131
|
-
# @see Alchemy::Picture::Url
|
132
103
|
def url_class
|
133
|
-
@_url_class ||= Alchemy
|
104
|
+
@_url_class ||= Alchemy.storage_adapter.picture_url_class
|
134
105
|
end
|
135
106
|
|
136
107
|
# Set a different picture url class
|
@@ -140,22 +111,16 @@ module Alchemy
|
|
140
111
|
@_url_class = klass
|
141
112
|
end
|
142
113
|
|
143
|
-
def
|
144
|
-
|
145
|
-
[
|
146
|
-
{
|
147
|
-
name: :by_file_format,
|
148
|
-
values: @_file_formats
|
149
|
-
},
|
150
|
-
{
|
151
|
-
name: :misc,
|
152
|
-
values: %w[recent last_upload without_tag deletable]
|
153
|
-
}
|
154
|
-
]
|
114
|
+
def searchable_alchemy_resource_attributes
|
115
|
+
Alchemy.storage_adapter.searchable_alchemy_resource_attributes(name)
|
155
116
|
end
|
156
117
|
|
157
|
-
def
|
158
|
-
|
118
|
+
def ransackable_attributes(_auth_object = nil)
|
119
|
+
Alchemy.storage_adapter.ransackable_attributes(name)
|
120
|
+
end
|
121
|
+
|
122
|
+
def ransackable_associations(_auth_object = nil)
|
123
|
+
Alchemy.storage_adapter.ransackable_associations(name)
|
159
124
|
end
|
160
125
|
|
161
126
|
def last_upload
|
@@ -164,33 +129,30 @@ module Alchemy
|
|
164
129
|
|
165
130
|
Picture.where(upload_hash: last_picture.upload_hash)
|
166
131
|
end
|
132
|
+
|
133
|
+
def ransackable_scopes(_auth_object = nil)
|
134
|
+
[:by_file_format, :recent, :last_upload, :without_tag, :deletable]
|
135
|
+
end
|
136
|
+
|
137
|
+
def file_formats(scope = all)
|
138
|
+
Alchemy.storage_adapter.file_formats(name, scope:)
|
139
|
+
end
|
167
140
|
end
|
168
141
|
|
169
142
|
# Instance methods
|
170
143
|
|
171
144
|
# Returns an url (or relative path) to a processed image for use inside an image_tag helper.
|
172
145
|
#
|
173
|
-
# Any additional options are passed to the url method, so you can add params to your url.
|
174
|
-
#
|
175
146
|
# Example:
|
176
147
|
#
|
177
148
|
# <%= image_tag picture.url(size: '320x200', format: 'png') %>
|
178
149
|
#
|
179
|
-
# @see Alchemy::PictureVariant#call for transformation options
|
180
|
-
# @see Alchemy::Picture::Url#call for url options
|
181
150
|
# @return [String|Nil]
|
182
151
|
def url(options = {})
|
183
152
|
return unless image_file
|
184
153
|
|
185
|
-
|
186
|
-
|
187
|
-
options.except(*TRANSFORMATION_OPTIONS).merge(
|
188
|
-
basename: name,
|
189
|
-
ext: variant.render_format,
|
190
|
-
name: name
|
191
|
-
)
|
192
|
-
)
|
193
|
-
rescue ::Dragonfly::Job::Fetch::NotFound => e
|
154
|
+
self.class.url_class.new(self).call(options)
|
155
|
+
rescue Alchemy.storage_adapter.rescuable_errors => e
|
194
156
|
log_warning(e.message)
|
195
157
|
nil
|
196
158
|
end
|
@@ -205,7 +167,7 @@ module Alchemy
|
|
205
167
|
|
206
168
|
url(
|
207
169
|
flatten: true,
|
208
|
-
format:
|
170
|
+
format: image_file_extension || "jpg",
|
209
171
|
size: size
|
210
172
|
)
|
211
173
|
end
|
@@ -239,18 +201,12 @@ module Alchemy
|
|
239
201
|
end
|
240
202
|
end
|
241
203
|
|
242
|
-
# Returns the suffix of the filename.
|
243
|
-
#
|
244
|
-
def suffix
|
245
|
-
image_file.ext
|
246
|
-
end
|
247
|
-
|
248
204
|
# Returns a humanized, readable name from image filename.
|
249
205
|
#
|
250
206
|
def humanized_name
|
251
207
|
return "" if image_file_name.blank?
|
252
208
|
|
253
|
-
convert_to_humanized_name(image_file_name,
|
209
|
+
convert_to_humanized_name(image_file_name, image_file_extension)
|
254
210
|
end
|
255
211
|
|
256
212
|
# Returns the format the image should be rendered with
|
@@ -260,9 +216,9 @@ module Alchemy
|
|
260
216
|
#
|
261
217
|
def default_render_format
|
262
218
|
if convertible?
|
263
|
-
|
219
|
+
Alchemy.config.image_output_format
|
264
220
|
else
|
265
|
-
|
221
|
+
image_file_extension
|
266
222
|
end
|
267
223
|
end
|
268
224
|
|
@@ -272,15 +228,15 @@ module Alchemy
|
|
272
228
|
# image has not a convertible file format (i.e. SVG) this returns +false+
|
273
229
|
#
|
274
230
|
def convertible?
|
275
|
-
|
276
|
-
|
231
|
+
Alchemy.config.image_output_format &&
|
232
|
+
Alchemy.config.image_output_format != "original" &&
|
277
233
|
has_convertible_format?
|
278
234
|
end
|
279
235
|
|
280
236
|
# Returns true if the image can be converted into other formats
|
281
237
|
#
|
282
238
|
def has_convertible_format?
|
283
|
-
|
239
|
+
Alchemy.storage_adapter.has_convertible_format?(self)
|
284
240
|
end
|
285
241
|
|
286
242
|
# Checks if the picture is restricted.
|
@@ -301,6 +257,32 @@ module Alchemy
|
|
301
257
|
picture_ingredients.empty?
|
302
258
|
end
|
303
259
|
|
260
|
+
def image_file_name
|
261
|
+
Alchemy.storage_adapter.image_file_name(self)
|
262
|
+
end
|
263
|
+
|
264
|
+
def image_file_format
|
265
|
+
Alchemy.storage_adapter.image_file_format(self)
|
266
|
+
end
|
267
|
+
|
268
|
+
def image_file_size
|
269
|
+
Alchemy.storage_adapter.image_file_size(self)
|
270
|
+
end
|
271
|
+
|
272
|
+
def image_file_width
|
273
|
+
Alchemy.storage_adapter.image_file_width(self)
|
274
|
+
end
|
275
|
+
|
276
|
+
def image_file_height
|
277
|
+
Alchemy.storage_adapter.image_file_height(self)
|
278
|
+
end
|
279
|
+
|
280
|
+
def image_file_extension
|
281
|
+
Alchemy.storage_adapter.image_file_extension(self)
|
282
|
+
end
|
283
|
+
alias_method :suffix, :image_file_extension
|
284
|
+
deprecate suffix: :image_file_extension, deprecator: Alchemy::Deprecation
|
285
|
+
|
304
286
|
# A size String from original image file values.
|
305
287
|
#
|
306
288
|
# == Example
|
@@ -310,5 +292,13 @@ module Alchemy
|
|
310
292
|
def image_file_dimensions
|
311
293
|
"#{image_file_width}x#{image_file_height}"
|
312
294
|
end
|
295
|
+
|
296
|
+
private
|
297
|
+
|
298
|
+
def image_file_type_allowed
|
299
|
+
unless image_file_extension&.in?(self.class.allowed_filetypes)
|
300
|
+
errors.add(:image_file, Alchemy.t("not a valid image"))
|
301
|
+
end
|
302
|
+
end
|
313
303
|
end
|
314
304
|
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
|