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
|
@@ -81,8 +81,8 @@ module Alchemy
|
|
|
81
81
|
#
|
|
82
82
|
def available_element_definitions(only_element_named = nil)
|
|
83
83
|
@_available_element_definitions ||= if only_element_named
|
|
84
|
-
|
|
85
|
-
element_definitions_by_name(
|
|
84
|
+
element_definition = Element.definition_by_name(only_element_named)
|
|
85
|
+
element_definitions_by_name(element_definition.nestable_elements)
|
|
86
86
|
else
|
|
87
87
|
element_definitions.dup
|
|
88
88
|
end
|
|
@@ -100,7 +100,7 @@ module Alchemy
|
|
|
100
100
|
# All names of elements that can actually be placed on current page.
|
|
101
101
|
#
|
|
102
102
|
def available_element_names
|
|
103
|
-
@_available_element_names ||= available_element_definitions.map
|
|
103
|
+
@_available_element_names ||= available_element_definitions.map(&:name)
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
# Available element definitions excluding nested unique elements.
|
|
@@ -109,7 +109,7 @@ module Alchemy
|
|
|
109
109
|
@_available_elements = if parent
|
|
110
110
|
parents_unique_nested_elements = parent.nested_elements.where(unique: true).pluck(:name)
|
|
111
111
|
available_element_definitions(parent.name).reject do |e|
|
|
112
|
-
parents_unique_nested_elements.include?
|
|
112
|
+
parents_unique_nested_elements.include?(e.name)
|
|
113
113
|
end
|
|
114
114
|
else
|
|
115
115
|
available_element_definitions
|
|
@@ -129,10 +129,9 @@ module Alchemy
|
|
|
129
129
|
#
|
|
130
130
|
def descendent_element_definitions
|
|
131
131
|
definitions = element_definitions_by_name(element_definition_names)
|
|
132
|
-
definitions.select {
|
|
133
|
-
|
|
134
|
-
end
|
|
135
|
-
definitions.uniq { |d| d["name"] }
|
|
132
|
+
definitions.select { _1.nestable_elements.any? }.flat_map do |d|
|
|
133
|
+
element_definitions_by_name(d.nestable_elements)
|
|
134
|
+
end.uniq(&:name)
|
|
136
135
|
end
|
|
137
136
|
|
|
138
137
|
# All names of elements that are defined in the page definition.
|
|
@@ -145,7 +144,7 @@ module Alchemy
|
|
|
145
144
|
# elements: [headline, contactform]
|
|
146
145
|
#
|
|
147
146
|
def element_definition_names
|
|
148
|
-
definition
|
|
147
|
+
definition.elements || []
|
|
149
148
|
end
|
|
150
149
|
|
|
151
150
|
# Element definitions with given name(s)
|
|
@@ -161,7 +160,7 @@ module Alchemy
|
|
|
161
160
|
if names.to_s == "all"
|
|
162
161
|
Element.definitions
|
|
163
162
|
else
|
|
164
|
-
Element.definitions.select {
|
|
163
|
+
Element.definitions.select { names.include?(_1.name) }
|
|
165
164
|
end
|
|
166
165
|
end
|
|
167
166
|
|
|
@@ -181,7 +180,7 @@ module Alchemy
|
|
|
181
180
|
# And if so, it generates them.
|
|
182
181
|
#
|
|
183
182
|
def generate_elements
|
|
184
|
-
definition.
|
|
183
|
+
definition.autogenerate&.each do |element_name|
|
|
185
184
|
Element.create(page: self, page_version: draft_version, name: element_name)
|
|
186
185
|
end
|
|
187
186
|
end
|
|
@@ -190,7 +189,7 @@ module Alchemy
|
|
|
190
189
|
#
|
|
191
190
|
def delete_unique_element_definitions!
|
|
192
191
|
@_available_element_definitions.delete_if do |element|
|
|
193
|
-
element
|
|
192
|
+
element.unique && @_existing_element_names.include?(element.name)
|
|
194
193
|
end
|
|
195
194
|
end
|
|
196
195
|
|
|
@@ -198,8 +197,8 @@ module Alchemy
|
|
|
198
197
|
#
|
|
199
198
|
def delete_outnumbered_element_definitions!
|
|
200
199
|
@_available_element_definitions.delete_if do |element|
|
|
201
|
-
outnumbered = @_existing_element_names.select { |name| name == element
|
|
202
|
-
element
|
|
200
|
+
outnumbered = @_existing_element_names.select { |name| name == element.name }
|
|
201
|
+
element.amount && outnumbered.count >= element.amount
|
|
203
202
|
end
|
|
204
203
|
end
|
|
205
204
|
end
|
|
@@ -38,13 +38,13 @@ module Alchemy
|
|
|
38
38
|
# @returns Array
|
|
39
39
|
#
|
|
40
40
|
def has_limited_editors?
|
|
41
|
-
definition
|
|
41
|
+
definition.editable_by.present?
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def editor_roles
|
|
45
45
|
return unless has_limited_editors?
|
|
46
46
|
|
|
47
|
-
definition
|
|
47
|
+
definition.editable_by
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# True if page locked_at timestamp and locked_by id are set
|
|
@@ -80,10 +80,10 @@ module Alchemy
|
|
|
80
80
|
|
|
81
81
|
# Returns the self#page_layout definition from config/alchemy/page_layouts.yml file.
|
|
82
82
|
def definition
|
|
83
|
-
definition =
|
|
83
|
+
definition = PageDefinition.get(page_layout)
|
|
84
84
|
if definition.nil?
|
|
85
85
|
log_warning "Page definition for `#{page_layout}` not found. Please check `page_layouts.yml` file."
|
|
86
|
-
return
|
|
86
|
+
return PageDefinition.new
|
|
87
87
|
end
|
|
88
88
|
definition
|
|
89
89
|
end
|
|
@@ -146,14 +146,14 @@ module Alchemy
|
|
|
146
146
|
def cache_page?
|
|
147
147
|
return false unless caching_enabled?
|
|
148
148
|
|
|
149
|
-
page_layout =
|
|
150
|
-
page_layout
|
|
149
|
+
page_layout = PageDefinition.get(self.page_layout)
|
|
150
|
+
page_layout.cache != false && page_layout.searchresults != true
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
private
|
|
154
154
|
|
|
155
155
|
def caching_enabled?
|
|
156
|
-
Alchemy
|
|
156
|
+
Alchemy.config.cache_pages &&
|
|
157
157
|
Rails.application.config.action_controller.perform_caching
|
|
158
158
|
end
|
|
159
159
|
end
|
|
@@ -124,7 +124,7 @@ module Alchemy
|
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
def ransackable_scopes(_auth_object)
|
|
127
|
-
[:published, :contentpages, :from_current_site, :searchables, :layoutpages]
|
|
127
|
+
[:published, :not_public, :restricted, :contentpages, :from_current_site, :searchables, :layoutpages, :by_page_layout]
|
|
128
128
|
end
|
|
129
129
|
end
|
|
130
130
|
end
|
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,56 +69,39 @@ 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
|
-
before_save :sanitize_image_file_name, if: :image_file_name
|
|
98
|
-
# Create important thumbnails upfront
|
|
99
|
-
after_create -> { PictureThumb.generate_thumbs!(self) if has_convertible_format? }
|
|
72
|
+
include Alchemy.storage_adapter.picture_class_methods
|
|
100
73
|
|
|
101
74
|
# We need to define this method here to have it available in the validations below.
|
|
102
75
|
class << self
|
|
103
76
|
def allowed_filetypes
|
|
104
|
-
|
|
77
|
+
Alchemy.config.uploader.allowed_filetypes.alchemy_pictures
|
|
105
78
|
end
|
|
106
79
|
end
|
|
107
80
|
|
|
108
81
|
validates_presence_of :image_file
|
|
109
|
-
validates_size_of :image_file, maximum:
|
|
110
|
-
|
|
111
|
-
of: :image_file,
|
|
112
|
-
in: allowed_filetypes,
|
|
113
|
-
case_sensitive: false,
|
|
114
|
-
message: Alchemy.t("not a valid image")
|
|
82
|
+
validates_size_of :image_file, maximum: Alchemy.config.uploader.file_size_limit.megabytes
|
|
83
|
+
validate :image_file_type_allowed, if: -> { image_file.present? }
|
|
115
84
|
|
|
116
|
-
stampable stamper_class_name: Alchemy.
|
|
85
|
+
stampable stamper_class_name: Alchemy.user_class_name
|
|
117
86
|
|
|
118
87
|
scope :named, ->(name) { where("#{table_name}.name LIKE ?", "%#{name}%") }
|
|
119
88
|
scope :recent, -> { where("#{table_name}.created_at > ?", Time.current - 24.hours).order(:created_at) }
|
|
120
|
-
scope :deletable,
|
|
121
|
-
|
|
122
|
-
"#{table_name}.id NOT IN (SELECT related_object_id FROM alchemy_ingredients WHERE
|
|
123
|
-
|
|
124
|
-
)
|
|
125
|
-
end
|
|
89
|
+
scope :deletable,
|
|
90
|
+
-> {
|
|
91
|
+
where("#{table_name}.id NOT IN (SELECT related_object_id FROM alchemy_ingredients WHERE related_object_type = 'Alchemy::Picture')")
|
|
92
|
+
}
|
|
126
93
|
scope :without_tag, -> { left_outer_joins(:taggings).where(gutentag_taggings: {id: nil}) }
|
|
127
|
-
scope :by_file_format, ->(
|
|
94
|
+
scope :by_file_format, ->(file_format) do
|
|
95
|
+
Alchemy.storage_adapter.by_file_format_scope(file_format)
|
|
96
|
+
end
|
|
128
97
|
|
|
129
98
|
# Class methods
|
|
130
99
|
|
|
131
100
|
class << self
|
|
132
101
|
# The class used to generate URLs for pictures
|
|
133
102
|
#
|
|
134
|
-
# @see Alchemy::Picture::Url
|
|
135
103
|
def url_class
|
|
136
|
-
@_url_class ||= Alchemy
|
|
104
|
+
@_url_class ||= Alchemy.storage_adapter.picture_url_class
|
|
137
105
|
end
|
|
138
106
|
|
|
139
107
|
# Set a different picture url class
|
|
@@ -143,22 +111,16 @@ module Alchemy
|
|
|
143
111
|
@_url_class = klass
|
|
144
112
|
end
|
|
145
113
|
|
|
146
|
-
def
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
name: :misc,
|
|
155
|
-
values: %w[recent last_upload without_tag deletable]
|
|
156
|
-
}
|
|
157
|
-
]
|
|
114
|
+
def searchable_alchemy_resource_attributes
|
|
115
|
+
Alchemy.storage_adapter.searchable_alchemy_resource_attributes(name)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def ransackable_attributes(_auth_object = nil)
|
|
119
|
+
Alchemy.storage_adapter.ransackable_attributes(name)
|
|
158
120
|
end
|
|
159
121
|
|
|
160
|
-
def
|
|
161
|
-
|
|
122
|
+
def ransackable_associations(_auth_object = nil)
|
|
123
|
+
Alchemy.storage_adapter.ransackable_associations(name)
|
|
162
124
|
end
|
|
163
125
|
|
|
164
126
|
def last_upload
|
|
@@ -167,33 +129,30 @@ module Alchemy
|
|
|
167
129
|
|
|
168
130
|
Picture.where(upload_hash: last_picture.upload_hash)
|
|
169
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
|
|
170
140
|
end
|
|
171
141
|
|
|
172
142
|
# Instance methods
|
|
173
143
|
|
|
174
144
|
# Returns an url (or relative path) to a processed image for use inside an image_tag helper.
|
|
175
145
|
#
|
|
176
|
-
# Any additional options are passed to the url method, so you can add params to your url.
|
|
177
|
-
#
|
|
178
146
|
# Example:
|
|
179
147
|
#
|
|
180
148
|
# <%= image_tag picture.url(size: '320x200', format: 'png') %>
|
|
181
149
|
#
|
|
182
|
-
# @see Alchemy::PictureVariant#call for transformation options
|
|
183
|
-
# @see Alchemy::Picture::Url#call for url options
|
|
184
150
|
# @return [String|Nil]
|
|
185
151
|
def url(options = {})
|
|
186
152
|
return unless image_file
|
|
187
153
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
options.except(*TRANSFORMATION_OPTIONS).merge(
|
|
191
|
-
basename: name,
|
|
192
|
-
ext: variant.render_format,
|
|
193
|
-
name: name
|
|
194
|
-
)
|
|
195
|
-
)
|
|
196
|
-
rescue ::Dragonfly::Job::Fetch::NotFound => e
|
|
154
|
+
self.class.url_class.new(self).call(options)
|
|
155
|
+
rescue Alchemy.storage_adapter.rescuable_errors => e
|
|
197
156
|
log_warning(e.message)
|
|
198
157
|
nil
|
|
199
158
|
end
|
|
@@ -208,7 +167,7 @@ module Alchemy
|
|
|
208
167
|
|
|
209
168
|
url(
|
|
210
169
|
flatten: true,
|
|
211
|
-
format:
|
|
170
|
+
format: image_file_extension || "jpg",
|
|
212
171
|
size: size
|
|
213
172
|
)
|
|
214
173
|
end
|
|
@@ -242,18 +201,12 @@ module Alchemy
|
|
|
242
201
|
end
|
|
243
202
|
end
|
|
244
203
|
|
|
245
|
-
# Returns the suffix of the filename.
|
|
246
|
-
#
|
|
247
|
-
def suffix
|
|
248
|
-
image_file.ext
|
|
249
|
-
end
|
|
250
|
-
|
|
251
204
|
# Returns a humanized, readable name from image filename.
|
|
252
205
|
#
|
|
253
206
|
def humanized_name
|
|
254
207
|
return "" if image_file_name.blank?
|
|
255
208
|
|
|
256
|
-
convert_to_humanized_name(image_file_name,
|
|
209
|
+
convert_to_humanized_name(image_file_name, image_file_extension)
|
|
257
210
|
end
|
|
258
211
|
|
|
259
212
|
# Returns the format the image should be rendered with
|
|
@@ -263,9 +216,9 @@ module Alchemy
|
|
|
263
216
|
#
|
|
264
217
|
def default_render_format
|
|
265
218
|
if convertible?
|
|
266
|
-
|
|
219
|
+
Alchemy.config.image_output_format
|
|
267
220
|
else
|
|
268
|
-
|
|
221
|
+
image_file_extension
|
|
269
222
|
end
|
|
270
223
|
end
|
|
271
224
|
|
|
@@ -275,15 +228,15 @@ module Alchemy
|
|
|
275
228
|
# image has not a convertible file format (i.e. SVG) this returns +false+
|
|
276
229
|
#
|
|
277
230
|
def convertible?
|
|
278
|
-
|
|
279
|
-
|
|
231
|
+
Alchemy.config.image_output_format &&
|
|
232
|
+
Alchemy.config.image_output_format != "original" &&
|
|
280
233
|
has_convertible_format?
|
|
281
234
|
end
|
|
282
235
|
|
|
283
236
|
# Returns true if the image can be converted into other formats
|
|
284
237
|
#
|
|
285
238
|
def has_convertible_format?
|
|
286
|
-
|
|
239
|
+
Alchemy.storage_adapter.has_convertible_format?(self)
|
|
287
240
|
end
|
|
288
241
|
|
|
289
242
|
# Checks if the picture is restricted.
|
|
@@ -304,6 +257,32 @@ module Alchemy
|
|
|
304
257
|
picture_ingredients.empty?
|
|
305
258
|
end
|
|
306
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
|
+
|
|
307
286
|
# A size String from original image file values.
|
|
308
287
|
#
|
|
309
288
|
# == Example
|
|
@@ -314,8 +293,12 @@ module Alchemy
|
|
|
314
293
|
"#{image_file_width}x#{image_file_height}"
|
|
315
294
|
end
|
|
316
295
|
|
|
317
|
-
|
|
318
|
-
|
|
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
|
|
319
302
|
end
|
|
320
303
|
end
|
|
321
304
|
end
|