alchemy_cms 5.2.0 → 6.0.0.b3
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/.github/workflows/ci.yml +6 -14
- data/.gitignore +0 -1
- data/.hound.yml +1 -1
- data/.rubocop.yml +46 -4
- data/CHANGELOG.md +114 -5
- data/Gemfile +8 -1
- data/README.md +5 -2
- data/alchemy_cms.gemspec +78 -65
- data/app/assets/javascripts/alchemy/admin.js +0 -2
- data/app/assets/javascripts/alchemy/alchemy.base.js.coffee +0 -27
- data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +2 -1
- data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +1 -1
- data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +0 -25
- data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +1 -1
- data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +2 -0
- data/app/assets/javascripts/alchemy/alchemy.fixed_elements.js +1 -1
- data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +3 -1
- data/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee +1 -1
- data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +40 -27
- data/app/assets/javascripts/alchemy/templates/node_folder.hbs +1 -1
- data/app/assets/stylesheets/alchemy/_extends.scss +15 -2
- data/app/assets/stylesheets/alchemy/admin.scss +1 -1
- data/app/assets/stylesheets/alchemy/archive.scss +20 -5
- data/app/assets/stylesheets/alchemy/buttons.scss +0 -4
- data/app/assets/stylesheets/alchemy/elements.scss +73 -61
- data/app/assets/stylesheets/alchemy/images.scss +8 -0
- data/app/assets/stylesheets/alchemy/node-select.scss +4 -3
- data/app/assets/stylesheets/alchemy/page-select.scss +1 -0
- data/app/controllers/alchemy/admin/attachments_controller.rb +8 -4
- data/app/controllers/alchemy/admin/base_controller.rb +5 -7
- data/app/controllers/alchemy/admin/elements_controller.rb +59 -34
- data/app/controllers/alchemy/admin/essence_audios_controller.rb +30 -0
- data/app/controllers/alchemy/admin/essence_files_controller.rb +0 -14
- data/app/controllers/alchemy/admin/essence_pictures_controller.rb +8 -79
- data/app/controllers/alchemy/admin/essence_videos_controller.rb +33 -0
- data/app/controllers/alchemy/admin/ingredients_controller.rb +30 -0
- data/app/controllers/alchemy/admin/layoutpages_controller.rb +0 -1
- data/app/controllers/alchemy/admin/pages_controller.rb +7 -22
- data/app/controllers/alchemy/admin/pictures_controller.rb +56 -17
- data/app/controllers/alchemy/admin/resources_controller.rb +84 -10
- data/app/controllers/alchemy/api/elements_controller.rb +13 -4
- data/app/controllers/alchemy/api/pages_controller.rb +4 -3
- data/app/controllers/concerns/alchemy/admin/archive_overlay.rb +13 -3
- data/app/controllers/concerns/alchemy/admin/crop_action.rb +26 -0
- data/app/decorators/alchemy/element_editor.rb +26 -1
- data/app/decorators/alchemy/ingredient_editor.rb +158 -0
- data/app/helpers/alchemy/admin/elements_helper.rb +1 -0
- data/app/helpers/alchemy/admin/essences_helper.rb +1 -1
- data/app/helpers/alchemy/admin/ingredients_helper.rb +42 -0
- data/app/helpers/alchemy/elements_block_helper.rb +23 -6
- data/app/helpers/alchemy/elements_helper.rb +12 -5
- data/app/helpers/alchemy/pages_helper.rb +3 -11
- data/app/jobs/alchemy/base_job.rb +11 -0
- data/app/jobs/alchemy/publish_page_job.rb +11 -0
- data/app/models/alchemy/attachment.rb +24 -7
- data/app/models/alchemy/content.rb +1 -6
- data/app/models/alchemy/content/factory.rb +23 -27
- data/app/models/alchemy/element.rb +39 -72
- data/app/models/alchemy/element/definitions.rb +29 -27
- data/app/models/alchemy/element/element_contents.rb +131 -122
- data/app/models/alchemy/element/element_essences.rb +111 -98
- data/app/models/alchemy/element/element_ingredients.rb +184 -0
- data/app/models/alchemy/element/presenters.rb +104 -85
- data/app/models/alchemy/elements_repository.rb +126 -0
- data/app/models/alchemy/essence_audio.rb +12 -0
- data/app/models/alchemy/essence_headline.rb +40 -0
- data/app/models/alchemy/essence_picture.rb +4 -116
- data/app/models/alchemy/essence_richtext.rb +12 -0
- data/app/models/alchemy/essence_video.rb +12 -0
- data/app/models/alchemy/image_cropper_settings.rb +87 -0
- data/app/models/alchemy/ingredient.rb +183 -0
- data/app/models/alchemy/ingredient_validator.rb +97 -0
- data/app/models/alchemy/ingredients/audio.rb +29 -0
- data/app/models/alchemy/ingredients/boolean.rb +21 -0
- data/app/models/alchemy/ingredients/datetime.rb +20 -0
- data/app/models/alchemy/ingredients/file.rb +30 -0
- data/app/models/alchemy/ingredients/headline.rb +42 -0
- data/app/models/alchemy/ingredients/html.rb +19 -0
- data/app/models/alchemy/ingredients/link.rb +16 -0
- data/app/models/alchemy/ingredients/node.rb +23 -0
- data/app/models/alchemy/ingredients/page.rb +23 -0
- data/app/models/alchemy/ingredients/picture.rb +41 -0
- data/app/models/alchemy/ingredients/richtext.rb +57 -0
- data/app/models/alchemy/ingredients/select.rb +10 -0
- data/app/models/alchemy/ingredients/text.rb +17 -0
- data/app/models/alchemy/ingredients/video.rb +33 -0
- data/app/models/alchemy/language.rb +0 -11
- data/app/models/alchemy/page.rb +76 -33
- data/app/models/alchemy/page/fixed_attributes.rb +53 -51
- data/app/models/alchemy/page/page_elements.rb +186 -205
- data/app/models/alchemy/page/page_naming.rb +66 -64
- data/app/models/alchemy/page/page_natures.rb +139 -142
- data/app/models/alchemy/page/page_scopes.rb +117 -102
- data/app/models/alchemy/page/publisher.rb +50 -0
- data/app/models/alchemy/page/url_path.rb +1 -1
- data/app/models/alchemy/page_version.rb +58 -0
- data/app/models/alchemy/picture.rb +18 -40
- data/app/models/alchemy/picture/calculations.rb +2 -8
- data/app/models/alchemy/picture/preprocessor.rb +2 -0
- data/app/models/alchemy/picture/transformations.rb +24 -96
- data/app/models/concerns/alchemy/picture_thumbnails.rb +181 -0
- data/app/models/concerns/alchemy/touch_elements.rb +2 -2
- data/app/presenters/alchemy/picture_view.rb +88 -0
- data/app/serializers/alchemy/element_serializer.rb +5 -0
- data/app/serializers/alchemy/page_tree_serializer.rb +3 -2
- data/app/services/alchemy/delete_elements.rb +44 -0
- data/app/services/alchemy/duplicate_element.rb +56 -0
- data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +2 -3
- data/app/views/alchemy/admin/attachments/_file_to_assign.html.erb +3 -3
- data/app/views/alchemy/admin/attachments/assign.js.erb +11 -0
- data/app/views/alchemy/admin/attachments/index.html.erb +2 -3
- data/app/views/alchemy/admin/crop.html.erb +36 -0
- data/app/views/alchemy/admin/elements/_element.html.erb +14 -10
- data/app/views/alchemy/admin/elements/{_element_footer.html.erb → _footer.html.erb} +0 -0
- data/app/views/alchemy/admin/elements/{_new_element_form.html.erb → _form.html.erb} +1 -1
- data/app/views/alchemy/admin/elements/{_element_header.html.erb → _header.html.erb} +1 -1
- data/app/views/alchemy/admin/elements/{_element_toolbar.html.erb → _toolbar.html.erb} +5 -6
- data/app/views/alchemy/admin/elements/create.js.erb +1 -1
- data/app/views/alchemy/admin/elements/{trash.js.erb → destroy.js.erb} +2 -6
- data/app/views/alchemy/admin/elements/fold.js.erb +2 -2
- data/app/views/alchemy/admin/elements/new.html.erb +3 -3
- data/app/views/alchemy/admin/elements/order.js.erb +0 -17
- data/app/views/alchemy/admin/elements/update.js.erb +3 -2
- data/app/views/alchemy/admin/essence_audios/edit.html.erb +7 -0
- data/app/views/alchemy/admin/essence_pictures/update.js.erb +0 -1
- data/app/views/alchemy/admin/essence_videos/edit.html.erb +11 -0
- data/app/views/alchemy/admin/ingredients/_audio_fields.html.erb +4 -0
- data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +18 -0
- data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +25 -0
- data/app/views/alchemy/admin/ingredients/_video_fields.html.erb +8 -0
- data/app/views/alchemy/admin/ingredients/edit.html.erb +4 -0
- data/app/views/alchemy/admin/layoutpages/edit.html.erb +0 -5
- data/app/views/alchemy/admin/nodes/_node.html.erb +2 -2
- data/app/views/alchemy/admin/pages/_anchor_link.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_external_link.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_file_link.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_form.html.erb +0 -6
- data/app/views/alchemy/admin/pages/_internal_link.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_tinymce_custom_config.html.erb +5 -2
- data/app/views/alchemy/admin/pages/_toolbar.html.erb +1 -1
- data/app/views/alchemy/admin/pages/edit.html.erb +36 -24
- data/app/views/alchemy/admin/pages/index.html.erb +2 -9
- data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +2 -4
- data/app/views/alchemy/admin/partials/_routes.html.erb +7 -11
- data/app/views/alchemy/admin/partials/_search_form.html.erb +9 -0
- data/app/views/alchemy/admin/pictures/_archive.html.erb +1 -1
- data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +1 -1
- data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +5 -7
- data/app/views/alchemy/admin/pictures/_infos.html.erb +0 -1
- data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +4 -4
- data/app/views/alchemy/admin/pictures/assign.js.erb +10 -0
- data/app/views/alchemy/admin/pictures/index.html.erb +8 -3
- data/app/views/alchemy/admin/resources/_filter.html.erb +12 -0
- data/app/views/alchemy/admin/resources/_filter_bar.html.erb +14 -17
- data/app/views/alchemy/admin/resources/_form.html.erb +3 -0
- data/app/views/alchemy/admin/resources/_table_header.html.erb +15 -0
- data/app/views/alchemy/admin/resources/index.html.erb +3 -11
- data/app/views/alchemy/essences/_essence_audio_editor.html.erb +4 -0
- data/app/views/alchemy/essences/_essence_audio_view.html.erb +15 -0
- data/app/views/alchemy/essences/_essence_file_editor.html.erb +15 -6
- data/app/views/alchemy/essences/_essence_headline_editor.html.erb +36 -0
- data/app/views/alchemy/essences/_essence_headline_view.html.erb +10 -0
- data/app/views/alchemy/essences/_essence_link_editor.html.erb +8 -4
- data/app/views/alchemy/essences/_essence_picture_editor.html.erb +27 -12
- data/app/views/alchemy/essences/_essence_picture_view.html.erb +3 -3
- data/app/views/alchemy/essences/_essence_text_editor.html.erb +12 -4
- data/app/views/alchemy/essences/_essence_video_editor.html.erb +4 -0
- data/app/views/alchemy/essences/_essence_video_view.html.erb +18 -0
- data/app/views/alchemy/essences/shared/_essence_picture_tools.html.erb +21 -16
- data/app/views/alchemy/essences/shared/_linkable_essence_tools.html.erb +2 -2
- data/app/views/alchemy/ingredients/_audio_editor.html.erb +5 -0
- data/app/views/alchemy/ingredients/_audio_view.html.erb +14 -0
- data/app/views/alchemy/ingredients/_boolean_editor.html.erb +11 -0
- data/app/views/alchemy/ingredients/_boolean_view.html.erb +1 -0
- data/app/views/alchemy/ingredients/_datetime_editor.html.erb +17 -0
- data/app/views/alchemy/ingredients/_datetime_view.html.erb +9 -0
- data/app/views/alchemy/ingredients/_file_editor.html.erb +52 -0
- data/app/views/alchemy/ingredients/_file_view.html.erb +17 -0
- data/app/views/alchemy/ingredients/_headline_editor.html.erb +30 -0
- data/app/views/alchemy/ingredients/_headline_view.html.erb +9 -0
- data/app/views/alchemy/ingredients/_html_editor.html.erb +8 -0
- data/app/views/alchemy/ingredients/_html_view.html.erb +1 -0
- data/app/views/alchemy/ingredients/_link_editor.html.erb +24 -0
- data/app/views/alchemy/ingredients/_link_view.html.erb +9 -0
- data/app/views/alchemy/ingredients/_node_editor.html.erb +26 -0
- data/app/views/alchemy/ingredients/_node_view.html.erb +1 -0
- data/app/views/alchemy/ingredients/_page_editor.html.erb +25 -0
- data/app/views/alchemy/ingredients/_page_view.html.erb +4 -0
- data/app/views/alchemy/ingredients/_picture_editor.html.erb +60 -0
- data/app/views/alchemy/ingredients/_picture_view.html.erb +5 -0
- data/app/views/alchemy/ingredients/_richtext_editor.html.erb +12 -0
- data/app/views/alchemy/ingredients/_richtext_view.html.erb +3 -0
- data/app/views/alchemy/ingredients/_select_editor.html.erb +30 -0
- data/app/views/alchemy/ingredients/_select_view.html.erb +1 -0
- data/app/views/alchemy/ingredients/_text_editor.html.erb +20 -0
- data/app/views/alchemy/ingredients/_text_view.html.erb +16 -0
- data/app/views/alchemy/ingredients/_video_editor.html.erb +5 -0
- data/app/views/alchemy/ingredients/_video_view.html.erb +17 -0
- data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +20 -0
- data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +57 -0
- data/config/brakeman.ignore +66 -159
- data/config/initializers/dragonfly.rb +10 -0
- data/config/locales/alchemy.en.yml +108 -64
- data/config/routes.rb +17 -22
- data/db/migrate/20201207131309_create_page_versions.rb +19 -0
- data/db/migrate/20201207135820_add_page_version_id_to_alchemy_elements.rb +76 -0
- data/db/migrate/20210205143548_rename_public_on_and_public_until_on_alchemy_pages.rb +10 -0
- data/db/migrate/20210326105046_add_sanitized_body_to_alchemy_essence_richtexts.rb +7 -0
- data/db/migrate/20210406093436_add_alchemy_essence_headlines.rb +12 -0
- data/db/migrate/20210506135919_create_essence_audios.rb +19 -0
- data/db/migrate/20210506140258_create_essence_videos.rb +23 -0
- data/db/migrate/20210508091432_create_alchemy_ingredients.rb +22 -0
- data/lib/alchemy/admin/preview_url.rb +2 -0
- data/lib/alchemy/deprecation.rb +1 -1
- data/lib/alchemy/dragonfly/processors/auto_orient.rb +18 -0
- data/lib/alchemy/dragonfly/processors/crop_resize.rb +35 -0
- data/lib/alchemy/elements_finder.rb +14 -60
- data/lib/alchemy/essence.rb +1 -2
- data/lib/alchemy/forms/builder.rb +21 -1
- data/lib/alchemy/hints.rb +8 -4
- data/lib/alchemy/page_layout.rb +0 -13
- data/lib/alchemy/permissions.rb +30 -29
- data/lib/alchemy/resource.rb +13 -3
- data/lib/alchemy/resource_filter.rb +40 -0
- data/lib/alchemy/resources_helper.rb +1 -16
- data/lib/alchemy/tasks/tidy.rb +29 -0
- data/lib/alchemy/test_support.rb +2 -11
- data/lib/alchemy/test_support/essence_shared_examples.rb +0 -1
- data/lib/alchemy/test_support/factories/element_factory.rb +8 -8
- data/lib/alchemy/test_support/factories/essence_audio_factory.rb +7 -0
- data/lib/alchemy/test_support/factories/essence_video_factory.rb +7 -0
- data/lib/alchemy/test_support/factories/ingredient_factory.rb +25 -0
- data/lib/alchemy/test_support/factories/page_factory.rb +20 -1
- data/lib/alchemy/test_support/factories/page_version_factory.rb +23 -0
- data/lib/alchemy/test_support/having_crop_action_examples.rb +170 -0
- data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +646 -0
- data/lib/alchemy/test_support/shared_ingredient_editor_examples.rb +21 -0
- data/lib/alchemy/test_support/shared_ingredient_examples.rb +75 -0
- data/lib/alchemy/tinymce.rb +17 -0
- data/lib/alchemy/upgrader/six_point_zero.rb +21 -0
- data/lib/alchemy/upgrader/tasks/add_page_versions.rb +33 -0
- data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +62 -0
- data/lib/alchemy/version.rb +1 -1
- data/lib/alchemy_cms.rb +1 -0
- data/lib/generators/alchemy/elements/elements_generator.rb +1 -0
- data/lib/generators/alchemy/elements/templates/view.html.erb +9 -0
- data/lib/generators/alchemy/elements/templates/view.html.haml +9 -0
- data/lib/generators/alchemy/elements/templates/view.html.slim +9 -0
- data/lib/generators/alchemy/ingredient/ingredient_generator.rb +38 -0
- data/lib/generators/alchemy/ingredient/templates/editor.html.erb +14 -0
- data/lib/generators/alchemy/ingredient/templates/model.rb.tt +13 -0
- data/lib/generators/alchemy/ingredient/templates/view.html.erb +1 -0
- data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +1 -1
- data/lib/generators/alchemy/menus/templates/node.html.erb +1 -1
- data/lib/generators/alchemy/menus/templates/node.html.haml +1 -1
- data/lib/generators/alchemy/menus/templates/node.html.slim +1 -1
- data/lib/generators/alchemy/menus/templates/wrapper.html.erb +1 -1
- data/lib/generators/alchemy/menus/templates/wrapper.html.haml +1 -1
- data/lib/generators/alchemy/menus/templates/wrapper.html.slim +1 -1
- data/lib/tasks/alchemy/thumbnails.rake +4 -2
- data/lib/tasks/alchemy/tidy.rake +12 -0
- data/lib/tasks/alchemy/upgrade.rake +26 -0
- data/package.json +3 -2
- data/package/admin.js +11 -1
- data/package/src/__tests__/i18n.spec.js +23 -0
- data/package/src/file_editors.js +28 -0
- data/package/src/i18n.js +1 -3
- data/package/src/image_cropper.js +103 -0
- data/package/src/image_loader.js +58 -0
- data/package/src/node_tree.js +5 -5
- data/package/src/picture_editors.js +169 -0
- data/package/src/utils/__tests__/ajax.spec.js +20 -12
- data/package/src/utils/ajax.js +8 -3
- data/vendor/assets/javascripts/jquery_plugins/jquery.Jcrop.min.js +3 -18
- data/vendor/assets/stylesheets/jquery.Jcrop.min.scss +2 -28
- metadata +292 -55
- data/app/assets/javascripts/alchemy/alchemy.image_cropper.js.coffee +0 -44
- data/app/assets/javascripts/alchemy/alchemy.trash_window.js.coffee +0 -30
- data/app/assets/stylesheets/alchemy/trash.scss +0 -8
- data/app/controllers/alchemy/admin/trash_controller.rb +0 -44
- data/app/views/alchemy/admin/attachments/_filter_bar.html.erb +0 -29
- data/app/views/alchemy/admin/essence_files/assign.js.erb +0 -3
- data/app/views/alchemy/admin/essence_pictures/assign.js.erb +0 -4
- data/app/views/alchemy/admin/essence_pictures/crop.html.erb +0 -48
- data/app/views/alchemy/admin/pictures/_filter_bar.html.erb +0 -30
- data/app/views/alchemy/admin/trash/clear.js.erb +0 -4
- data/app/views/alchemy/admin/trash/index.html.erb +0 -31
- data/lib/alchemy/test_support/factories.rb +0 -16
|
@@ -8,10 +8,11 @@ module Alchemy
|
|
|
8
8
|
#
|
|
9
9
|
def index
|
|
10
10
|
# Fix for cancancan not able to merge multiple AR scopes for logged in users
|
|
11
|
-
if
|
|
12
|
-
@pages = Page.
|
|
11
|
+
if cannot? :edit_content, Alchemy::Page
|
|
12
|
+
@pages = Alchemy::Page.accessible_by(current_ability, :index)
|
|
13
|
+
@pages = @pages.where(language: Language.current)
|
|
13
14
|
else
|
|
14
|
-
@pages = Page.
|
|
15
|
+
@pages = Language.current&.pages.presence || Alchemy::Page.none
|
|
15
16
|
end
|
|
16
17
|
@pages = @pages.includes(*page_includes)
|
|
17
18
|
@pages = @pages.ransack(params[:q]).result
|
|
@@ -2,18 +2,28 @@
|
|
|
2
2
|
module Alchemy
|
|
3
3
|
module Admin
|
|
4
4
|
module ArchiveOverlay
|
|
5
|
+
# Sets assignable id on given form field via JS.
|
|
6
|
+
#
|
|
7
|
+
# When the user saves the model the assignable
|
|
8
|
+
# gets persisted with the model as well.
|
|
9
|
+
#
|
|
10
|
+
def assign
|
|
11
|
+
@assignable_id = params[:id]
|
|
12
|
+
@form_field_id = params[:form_field_id]
|
|
13
|
+
end
|
|
14
|
+
|
|
5
15
|
private
|
|
6
16
|
|
|
7
17
|
def in_overlay?
|
|
8
|
-
params[:
|
|
18
|
+
params[:form_field_id].present?
|
|
9
19
|
end
|
|
10
20
|
|
|
11
21
|
def archive_overlay
|
|
12
|
-
@
|
|
22
|
+
@form_field_id = params[:form_field_id]
|
|
13
23
|
|
|
14
24
|
respond_to do |format|
|
|
15
25
|
format.html { render partial: "archive_overlay" }
|
|
16
|
-
format.js
|
|
26
|
+
format.js { render action: "archive_overlay" }
|
|
17
27
|
end
|
|
18
28
|
end
|
|
19
29
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemy
|
|
4
|
+
module Admin
|
|
5
|
+
module CropAction
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
before_action :load_croppable_resource, only: [:crop]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def crop
|
|
13
|
+
@picture = Alchemy::Picture.find_by(id: params[:picture_id])
|
|
14
|
+
if @picture
|
|
15
|
+
@croppable_resource.picture = @picture
|
|
16
|
+
@settings = @croppable_resource.image_cropper_settings
|
|
17
|
+
@element = @croppable_resource.element
|
|
18
|
+
else
|
|
19
|
+
@no_image_notice = Alchemy.t(:no_image_for_cropper_found)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
render template: "alchemy/admin/crop"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -19,6 +19,23 @@ module Alchemy
|
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# Returns ingredient editor instances for defined ingredients
|
|
23
|
+
#
|
|
24
|
+
# Creates ingredient on demand if the ingredient is not yet present on the element
|
|
25
|
+
#
|
|
26
|
+
# @return Array<Alchemy::IngredientEditor>
|
|
27
|
+
def ingredients
|
|
28
|
+
element.definition.fetch(:ingredients, []).map do |ingredient|
|
|
29
|
+
Alchemy::IngredientEditor.new(find_or_create_ingredient(ingredient))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Are any ingredients defined?
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
def has_ingredients_defined?
|
|
36
|
+
element.definition.fetch(:ingredients, []).any?
|
|
37
|
+
end
|
|
38
|
+
|
|
22
39
|
# CSS classes for the element editor partial.
|
|
23
40
|
def css_classes
|
|
24
41
|
[
|
|
@@ -38,7 +55,7 @@ module Alchemy
|
|
|
38
55
|
def editable?
|
|
39
56
|
return false if folded?
|
|
40
57
|
|
|
41
|
-
content_definitions.present? || taggable?
|
|
58
|
+
content_definitions.present? || ingredient_definitions.any? || taggable?
|
|
42
59
|
end
|
|
43
60
|
|
|
44
61
|
# Fixes Rails partial renderer calling to_model on the object
|
|
@@ -103,5 +120,13 @@ module Alchemy
|
|
|
103
120
|
def create_content(name)
|
|
104
121
|
Alchemy::Content.create(element: element, name: name)
|
|
105
122
|
end
|
|
123
|
+
|
|
124
|
+
def find_or_create_ingredient(definition)
|
|
125
|
+
element.ingredients.detect { |i| i.role == definition[:role] } ||
|
|
126
|
+
element.ingredients.create!(
|
|
127
|
+
role: definition[:role],
|
|
128
|
+
type: Alchemy::Ingredient.normalize_type(definition[:type]),
|
|
129
|
+
)
|
|
130
|
+
end
|
|
106
131
|
end
|
|
107
132
|
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemy
|
|
4
|
+
class IngredientEditor < SimpleDelegator
|
|
5
|
+
alias_method :ingredient, :__getobj__
|
|
6
|
+
|
|
7
|
+
def to_partial_path
|
|
8
|
+
"alchemy/ingredients/#{partial_name}_editor"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Returns the translated role for displaying in labels
|
|
12
|
+
#
|
|
13
|
+
# Translate it in your locale yml file:
|
|
14
|
+
#
|
|
15
|
+
# alchemy:
|
|
16
|
+
# ingredient_roles:
|
|
17
|
+
# foo: Bar
|
|
18
|
+
#
|
|
19
|
+
# Optionally you can scope your ingredient role to an element:
|
|
20
|
+
#
|
|
21
|
+
# alchemy:
|
|
22
|
+
# ingredient_roles:
|
|
23
|
+
# article:
|
|
24
|
+
# foo: Baz
|
|
25
|
+
#
|
|
26
|
+
def translated_role
|
|
27
|
+
Alchemy.t(
|
|
28
|
+
role,
|
|
29
|
+
scope: "ingredient_roles.#{element.name}",
|
|
30
|
+
default: Alchemy.t("ingredient_roles.#{role}", default: role.humanize),
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def css_classes
|
|
35
|
+
[
|
|
36
|
+
"ingredient-editor",
|
|
37
|
+
partial_name,
|
|
38
|
+
deprecated? ? "deprecated" : nil,
|
|
39
|
+
].compact
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def data_attributes
|
|
43
|
+
{
|
|
44
|
+
ingredient_id: id,
|
|
45
|
+
ingredient_role: role,
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns a string to be passed to Rails form field tags to ensure it can be used with Rails' nested attributes.
|
|
50
|
+
#
|
|
51
|
+
# === Example:
|
|
52
|
+
#
|
|
53
|
+
# <%= text_field_tag text_editor.form_field_name, text_editor.value %>
|
|
54
|
+
#
|
|
55
|
+
# === Options:
|
|
56
|
+
#
|
|
57
|
+
# You can pass an Ingredient column_name. Default is 'value'
|
|
58
|
+
#
|
|
59
|
+
# ==== Example:
|
|
60
|
+
#
|
|
61
|
+
# <%= text_field_tag text_editor.form_field_name(:link), text_editor.value %>
|
|
62
|
+
#
|
|
63
|
+
def form_field_name(column = "value")
|
|
64
|
+
"element[ingredients_attributes][#{form_field_counter}][#{column}]"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Returns a unique string to be passed to a form field id.
|
|
68
|
+
#
|
|
69
|
+
# @param column [String] A Ingredient column_name. Default is 'value'
|
|
70
|
+
#
|
|
71
|
+
def form_field_id(column = "value")
|
|
72
|
+
"element_#{element.id}_ingredient_#{id}_#{column}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Fixes Rails partial renderer calling to_model on the object
|
|
76
|
+
# which reveals the delegated ingredient instead of this decorator.
|
|
77
|
+
def respond_to?(method_name)
|
|
78
|
+
return false if method_name == :to_model
|
|
79
|
+
|
|
80
|
+
super
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def has_warnings?
|
|
84
|
+
definition.blank? || deprecated?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def linked?
|
|
88
|
+
link.try(:present?)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def warnings
|
|
92
|
+
return unless has_warnings?
|
|
93
|
+
|
|
94
|
+
if definition.blank?
|
|
95
|
+
Logger.warn("ingredient #{role} is missing its definition", caller(1..1))
|
|
96
|
+
Alchemy.t(:ingredient_definition_missing)
|
|
97
|
+
else
|
|
98
|
+
deprecation_notice
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Returns a deprecation notice for ingredients marked deprecated
|
|
103
|
+
#
|
|
104
|
+
# You can either use localizations or pass a String as notice
|
|
105
|
+
# in the ingredient definition.
|
|
106
|
+
#
|
|
107
|
+
# == Custom deprecation notices
|
|
108
|
+
#
|
|
109
|
+
# Use general ingredient deprecation notice
|
|
110
|
+
#
|
|
111
|
+
# - name: element_name
|
|
112
|
+
# ingredients:
|
|
113
|
+
# - role: old_ingredient
|
|
114
|
+
# type: Text
|
|
115
|
+
# deprecated: true
|
|
116
|
+
#
|
|
117
|
+
# Add a translation to your locale file for a per ingredient notice.
|
|
118
|
+
#
|
|
119
|
+
# en:
|
|
120
|
+
# alchemy:
|
|
121
|
+
# ingredient_deprecation_notices:
|
|
122
|
+
# element_name:
|
|
123
|
+
# old_ingredient: Foo baz widget is deprecated
|
|
124
|
+
#
|
|
125
|
+
# or use the global translation that apply to all deprecated ingredients.
|
|
126
|
+
#
|
|
127
|
+
# en:
|
|
128
|
+
# alchemy:
|
|
129
|
+
# ingredient_deprecation_notice: Foo baz widget is deprecated
|
|
130
|
+
#
|
|
131
|
+
# or pass string as deprecation notice.
|
|
132
|
+
#
|
|
133
|
+
# - name: element_name
|
|
134
|
+
# ingredients:
|
|
135
|
+
# - role: old_ingredient
|
|
136
|
+
# type: Text
|
|
137
|
+
# deprecated: This ingredient will be removed soon.
|
|
138
|
+
#
|
|
139
|
+
def deprecation_notice
|
|
140
|
+
case definition[:deprecated]
|
|
141
|
+
when String
|
|
142
|
+
definition[:deprecated]
|
|
143
|
+
when TrueClass
|
|
144
|
+
Alchemy.t(
|
|
145
|
+
role,
|
|
146
|
+
scope: [:ingredient_deprecation_notices, element.name],
|
|
147
|
+
default: Alchemy.t(:ingredient_deprecated),
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
private
|
|
153
|
+
|
|
154
|
+
def form_field_counter
|
|
155
|
+
element.definition.fetch(:ingredients, []).index { |i| i[:role] == role }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemy
|
|
4
|
+
module Admin
|
|
5
|
+
module IngredientsHelper
|
|
6
|
+
include Alchemy::Admin::BaseHelper
|
|
7
|
+
|
|
8
|
+
# Renders the translated role of ingredient.
|
|
9
|
+
#
|
|
10
|
+
# Displays a warning icon if ingredient is missing its definition.
|
|
11
|
+
#
|
|
12
|
+
# Displays a mandatory field indicator, if the ingredient has validations.
|
|
13
|
+
#
|
|
14
|
+
def render_ingredient_role(ingredient)
|
|
15
|
+
if ingredient.blank?
|
|
16
|
+
warning("Ingredient is nil")
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
content = ingredient.translated_role
|
|
21
|
+
|
|
22
|
+
if ingredient.has_warnings?
|
|
23
|
+
icon = hint_with_tooltip(ingredient.warnings)
|
|
24
|
+
content = "#{icon} #{content}".html_safe
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if ingredient.has_validations?
|
|
28
|
+
"#{content}<span class='validation_indicator'>*</span>".html_safe
|
|
29
|
+
else
|
|
30
|
+
content
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Renders the label and hint for a ingredient.
|
|
35
|
+
def ingredient_label(ingredient, column = :value)
|
|
36
|
+
label_tag ingredient.form_field_id(column) do
|
|
37
|
+
[render_ingredient_role(ingredient), render_hint_for(ingredient)].compact.join(" ").html_safe
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -26,12 +26,13 @@ module Alchemy
|
|
|
26
26
|
class ElementViewHelper < BlockHelper
|
|
27
27
|
# Renders one of the element's contents.
|
|
28
28
|
#
|
|
29
|
+
# If the element uses +ingredients+ it renders the ingredient record.
|
|
30
|
+
#
|
|
29
31
|
def render(name, options = {}, html_options = {})
|
|
30
|
-
|
|
31
|
-
return if
|
|
32
|
+
renderable = element.ingredient_by_role(name) || content(name)
|
|
33
|
+
return if renderable.nil?
|
|
32
34
|
|
|
33
|
-
helpers.render(
|
|
34
|
-
content: content,
|
|
35
|
+
helpers.render(renderable, {
|
|
35
36
|
options: options,
|
|
36
37
|
html_options: html_options,
|
|
37
38
|
})
|
|
@@ -43,16 +44,30 @@ module Alchemy
|
|
|
43
44
|
element.content_by_name(name)
|
|
44
45
|
end
|
|
45
46
|
|
|
47
|
+
deprecate content: "Use `ingredient_by_role` instead", deprecator: Alchemy::Deprecation
|
|
48
|
+
|
|
46
49
|
# Returns the ingredient of one of the element's contents.
|
|
47
50
|
#
|
|
51
|
+
# If the element uses +ingredients+ it returns the +value+ of the ingredient record.
|
|
52
|
+
#
|
|
48
53
|
def ingredient(name)
|
|
49
54
|
element.ingredient(name)
|
|
50
55
|
end
|
|
51
56
|
|
|
52
|
-
# Returns
|
|
57
|
+
# Returns the value of one of the element's ingredients.
|
|
58
|
+
#
|
|
59
|
+
def value(name)
|
|
60
|
+
element.value_for(name)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns true if the given content or ingredient has a value.
|
|
53
64
|
#
|
|
54
65
|
def has?(name)
|
|
55
|
-
element.
|
|
66
|
+
if element.ingredient_definitions.any?
|
|
67
|
+
element.has_value_for?(name)
|
|
68
|
+
else
|
|
69
|
+
element.has_ingredient?(name)
|
|
70
|
+
end
|
|
56
71
|
end
|
|
57
72
|
|
|
58
73
|
# Return's the given content's essence.
|
|
@@ -60,6 +75,8 @@ module Alchemy
|
|
|
60
75
|
def essence(name)
|
|
61
76
|
content(name).try(:essence)
|
|
62
77
|
end
|
|
78
|
+
|
|
79
|
+
deprecate essence: "Use `ingredient_by_role` instead", deprecator: Alchemy::Deprecation
|
|
63
80
|
end
|
|
64
81
|
|
|
65
82
|
# Block-level helper for element views. Constructs a DOM element wrapping
|
|
@@ -25,7 +25,7 @@ module Alchemy
|
|
|
25
25
|
# === Render elements from global page:
|
|
26
26
|
#
|
|
27
27
|
# <footer>
|
|
28
|
-
# <%= render_elements from_page: 'footer' %>
|
|
28
|
+
# <%= render_elements from_page: Alchemy::Page.find_by(page_layout: 'footer') %>
|
|
29
29
|
# </footer>
|
|
30
30
|
#
|
|
31
31
|
# === Custom elements finder:
|
|
@@ -50,8 +50,8 @@ module Alchemy
|
|
|
50
50
|
# <%= render_elements finder: MyCustomNewsArchive.new %>
|
|
51
51
|
# </div>
|
|
52
52
|
#
|
|
53
|
-
# @option options [Alchemy::Page
|
|
54
|
-
# The page the elements are rendered from.
|
|
53
|
+
# @option options [Alchemy::Page] :from_page (@page)
|
|
54
|
+
# The page the elements are rendered from.
|
|
55
55
|
# @option options [Array<String>|String] :only
|
|
56
56
|
# A list of element names only to be rendered.
|
|
57
57
|
# @option options [Array<String>|String] :except
|
|
@@ -77,7 +77,14 @@ module Alchemy
|
|
|
77
77
|
}.update(options)
|
|
78
78
|
|
|
79
79
|
finder = options[:finder] || Alchemy::ElementsFinder.new(options)
|
|
80
|
-
|
|
80
|
+
|
|
81
|
+
page_version = if @preview_mode
|
|
82
|
+
options[:from_page]&.draft_version
|
|
83
|
+
else
|
|
84
|
+
options[:from_page]&.public_version
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
elements = finder.elements(page_version: page_version)
|
|
81
88
|
|
|
82
89
|
buff = []
|
|
83
90
|
elements.each_with_index do |element, i|
|
|
@@ -133,7 +140,7 @@ module Alchemy
|
|
|
133
140
|
def render_element(element, options = {}, counter = 1)
|
|
134
141
|
if element.nil?
|
|
135
142
|
warning("Element is nil")
|
|
136
|
-
render "alchemy/elements/view_not_found", {name: "nil"}
|
|
143
|
+
render "alchemy/elements/view_not_found", { name: "nil" }
|
|
137
144
|
return
|
|
138
145
|
end
|
|
139
146
|
|