alchemy_cms 5.2.4 → 6.0.0.b1
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 +80 -25
- data/Gemfile +4 -2
- 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/admin.scss +1 -1
- data/app/assets/stylesheets/alchemy/archive.scss +4 -4
- 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/assets/stylesheets/tinymce/skins/alchemy/skin.min.css.scss +6 -6
- data/app/controllers/alchemy/admin/attachments_controller.rb +6 -2
- data/app/controllers/alchemy/admin/base_controller.rb +5 -7
- data/app/controllers/alchemy/admin/elements_controller.rb +58 -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 +6 -13
- data/app/controllers/alchemy/admin/pictures_controller.rb +35 -9
- data/app/controllers/alchemy/api/elements_controller.rb +10 -5
- data/app/controllers/alchemy/api/pages_controller.rb +2 -4
- 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 +23 -1
- data/app/decorators/alchemy/ingredient_editor.rb +154 -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 +22 -7
- 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 +1 -1
- data/app/models/alchemy/content/factory.rb +23 -27
- data/app/models/alchemy/content.rb +1 -6
- 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 +100 -98
- data/app/models/alchemy/element/element_ingredients.rb +176 -0
- data/app/models/alchemy/element/presenters.rb +89 -87
- data/app/models/alchemy/element.rb +40 -73
- 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 +219 -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/node.rb +1 -1
- 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 +113 -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.rb +67 -33
- data/app/models/alchemy/page_version.rb +58 -0
- 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/alchemy/picture.rb +4 -2
- 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 +1 -2
- 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/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/{trash.js.erb → destroy.js.erb} +1 -3
- 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/edit.html.erb +36 -24
- 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/pictures/_filter_and_size_bar.html.erb +4 -8
- 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/resources/_form.html.erb +1 -0
- 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_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 +50 -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 +25 -0
- data/app/views/alchemy/ingredients/_node_view.html.erb +1 -0
- data/app/views/alchemy/ingredients/_page_editor.html.erb +24 -0
- data/app/views/alchemy/ingredients/_page_view.html.erb +4 -0
- data/app/views/alchemy/ingredients/_picture_editor.html.erb +59 -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 +29 -0
- data/app/views/alchemy/ingredients/_select_view.html.erb +1 -0
- data/app/views/alchemy/ingredients/_text_editor.html.erb +19 -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 +23 -15
- 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/engine.rb +1 -1
- data/lib/alchemy/essence.rb +1 -2
- 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/tasks/tidy.rb +29 -0
- 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 +57 -0
- data/lib/alchemy/test_support.rb +2 -11
- data/lib/alchemy/tinymce.rb +17 -0
- data/lib/alchemy/upgrader/five_point_zero.rb +0 -32
- 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 +51 -0
- data/lib/alchemy/version.rb +1 -1
- 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/install_generator.rb +1 -2
- 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/tidy.rake +12 -0
- data/lib/tasks/alchemy/upgrade.rake +21 -15
- data/package/admin.js +9 -1
- data/package/src/file_editors.js +28 -0
- 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/package.json +3 -2
- data/vendor/assets/javascripts/jquery_plugins/jquery.Jcrop.min.js +3 -18
- data/vendor/assets/stylesheets/jquery.Jcrop.min.scss +2 -28
- metadata +285 -56
- 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/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/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 -20
|
@@ -1,151 +1,160 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Methods concerning contents for elements
|
|
4
|
-
#
|
|
5
3
|
module Alchemy
|
|
6
|
-
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
class Element < BaseRecord
|
|
5
|
+
# Methods concerning contents for elements
|
|
6
|
+
#
|
|
7
|
+
module ElementContents
|
|
8
|
+
# Find first content from element by given name.
|
|
9
|
+
def content_by_name(name)
|
|
10
|
+
contents_by_name(name).first
|
|
11
|
+
end
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
# Find first content from element by given essence type.
|
|
14
|
+
def content_by_type(essence_type)
|
|
15
|
+
contents_by_type(essence_type).first
|
|
16
|
+
end
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
# All contents from element by given name.
|
|
19
|
+
def contents_by_name(name)
|
|
20
|
+
contents.select { |content| content.name == name.to_s }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
alias_method :all_contents_by_name, :contents_by_name
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
# All contents from element by given essence type.
|
|
26
|
+
def contents_by_type(essence_type)
|
|
27
|
+
contents.select do |content|
|
|
28
|
+
content.essence_type == Content.normalize_essence_type(essence_type)
|
|
29
|
+
end
|
|
27
30
|
end
|
|
28
|
-
end
|
|
29
|
-
alias_method :all_contents_by_type, :contents_by_type
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
32
|
+
alias_method :all_contents_by_type, :contents_by_type
|
|
33
|
+
|
|
34
|
+
# Updates all related contents by calling +update_essence+ on each of them.
|
|
35
|
+
#
|
|
36
|
+
# @param contents_attributes [Hash]
|
|
37
|
+
# Hash of contents attributes.
|
|
38
|
+
# The keys has to be the #id of the content to update.
|
|
39
|
+
# The values a Hash of attribute names and values
|
|
40
|
+
#
|
|
41
|
+
# @return [Boolean]
|
|
42
|
+
# True if +errors+ are blank or +contents_attributes+ hash is nil
|
|
43
|
+
#
|
|
44
|
+
# == Example
|
|
45
|
+
#
|
|
46
|
+
# @element.update_contents(
|
|
47
|
+
# "1" => {ingredient: "Title"},
|
|
48
|
+
# "2" => {link: "https://google.com"}
|
|
49
|
+
# )
|
|
50
|
+
#
|
|
51
|
+
def update_contents(contents_attributes)
|
|
52
|
+
return true if contents_attributes.nil?
|
|
53
|
+
|
|
54
|
+
contents.each do |content|
|
|
55
|
+
content_hash = contents_attributes[content.id.to_s] || next
|
|
56
|
+
content.update_essence(content_hash) || errors.add(:base, :essence_validation_failed)
|
|
57
|
+
end
|
|
58
|
+
errors.blank?
|
|
59
|
+
end
|
|
50
60
|
|
|
51
|
-
contents
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
# Copy current content's contents to given target element
|
|
62
|
+
def copy_contents_to(element)
|
|
63
|
+
contents.map do |content|
|
|
64
|
+
Content.copy(content, element_id: element.id)
|
|
65
|
+
end
|
|
54
66
|
end
|
|
55
|
-
errors.blank?
|
|
56
|
-
end
|
|
57
67
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
# Returns the content that is marked as rss title.
|
|
69
|
+
#
|
|
70
|
+
# Mark a content as rss title in your +elements.yml+ file:
|
|
71
|
+
#
|
|
72
|
+
# - name: news
|
|
73
|
+
# contents:
|
|
74
|
+
# - name: headline
|
|
75
|
+
# type: EssenceText
|
|
76
|
+
# rss_title: true
|
|
77
|
+
#
|
|
78
|
+
def content_for_rss_title
|
|
79
|
+
content_for_rss_meta("title")
|
|
62
80
|
end
|
|
63
|
-
end
|
|
64
81
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
# Returns the content that is marked as rss description.
|
|
83
|
+
#
|
|
84
|
+
# Mark a content as rss description in your +elements.yml+ file:
|
|
85
|
+
#
|
|
86
|
+
# - name: news
|
|
87
|
+
# contents:
|
|
88
|
+
# - name: body
|
|
89
|
+
# type: EssenceRichtext
|
|
90
|
+
# rss_description: true
|
|
91
|
+
#
|
|
92
|
+
def content_for_rss_description
|
|
93
|
+
content_for_rss_meta("description")
|
|
94
|
+
end
|
|
78
95
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
#
|
|
83
|
-
# - name: news
|
|
84
|
-
# contents:
|
|
85
|
-
# - name: body
|
|
86
|
-
# type: EssenceRichtext
|
|
87
|
-
# rss_description: true
|
|
88
|
-
#
|
|
89
|
-
def content_for_rss_description
|
|
90
|
-
content_for_rss_meta("description")
|
|
91
|
-
end
|
|
96
|
+
# Returns the array with the hashes for all element contents in the elements.yml file
|
|
97
|
+
def content_definitions
|
|
98
|
+
return nil if definition.blank?
|
|
92
99
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return nil if definition.blank?
|
|
100
|
+
definition["contents"]
|
|
101
|
+
end
|
|
96
102
|
|
|
97
|
-
definition
|
|
98
|
-
|
|
103
|
+
# Returns the definition for given content_name
|
|
104
|
+
def content_definition_for(content_name)
|
|
105
|
+
if content_definitions.blank?
|
|
106
|
+
log_warning "Element #{name} is missing the content definition for #{content_name}"
|
|
107
|
+
nil
|
|
108
|
+
else
|
|
109
|
+
content_definitions.detect { |d| d["name"] == content_name.to_s }
|
|
110
|
+
end
|
|
111
|
+
end
|
|
99
112
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
# Returns an array of all EssenceRichtext contents ids from elements
|
|
114
|
+
#
|
|
115
|
+
# This is used to re-initialize the TinyMCE editor in the element editor.
|
|
116
|
+
#
|
|
117
|
+
def richtext_contents_ids
|
|
118
|
+
# This is not very efficient SQL wise I know, but we need to iterate
|
|
119
|
+
# recursivly through all descendent elements and I don't know how to do this
|
|
120
|
+
# in pure SQL. Anyone with a better idea is welcome to submit a patch.
|
|
121
|
+
ids = contents.select(&:has_tinymce?).collect(&:id)
|
|
122
|
+
expanded_nested_elements = nested_elements.expanded
|
|
123
|
+
if expanded_nested_elements.present?
|
|
124
|
+
ids += expanded_nested_elements.collect(&:richtext_contents_ids)
|
|
125
|
+
end
|
|
126
|
+
ids.flatten
|
|
107
127
|
end
|
|
108
|
-
end
|
|
109
128
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
#
|
|
114
|
-
def richtext_contents_ids
|
|
115
|
-
# This is not very efficient SQL wise I know, but we need to iterate
|
|
116
|
-
# recursivly through all descendent elements and I don't know how to do this
|
|
117
|
-
# in pure SQL. Anyone with a better idea is welcome to submit a patch.
|
|
118
|
-
ids = contents.select(&:has_tinymce?).collect(&:id)
|
|
119
|
-
expanded_nested_elements = nested_elements.expanded
|
|
120
|
-
if expanded_nested_elements.present?
|
|
121
|
-
ids += expanded_nested_elements.collect(&:richtext_contents_ids)
|
|
129
|
+
# True, if any of the element's contents has essence validations defined.
|
|
130
|
+
def has_validations?
|
|
131
|
+
!contents.detect(&:has_validations?).blank?
|
|
122
132
|
end
|
|
123
|
-
ids.flatten
|
|
124
|
-
end
|
|
125
133
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
# All element contents where the essence validation has failed.
|
|
135
|
+
def contents_with_errors
|
|
136
|
+
contents.select(&:essence_validation_failed?)
|
|
137
|
+
end
|
|
130
138
|
|
|
131
|
-
|
|
132
|
-
def contents_with_errors
|
|
133
|
-
contents.select(&:essence_validation_failed?)
|
|
134
|
-
end
|
|
139
|
+
private
|
|
135
140
|
|
|
136
|
-
|
|
141
|
+
def content_for_rss_meta(type)
|
|
142
|
+
definition = content_definitions.detect { |c| c["rss_#{type}"] }
|
|
143
|
+
return if definition.blank?
|
|
137
144
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return if definition.blank?
|
|
145
|
+
contents.detect { |content| content.name == definition["name"] }
|
|
146
|
+
end
|
|
141
147
|
|
|
142
|
-
contents
|
|
143
|
-
|
|
148
|
+
# creates the contents for this element as described in the elements.yml
|
|
149
|
+
#
|
|
150
|
+
# If ingredients are defined as well no contents get created,
|
|
151
|
+
# ingredients get created instead.
|
|
152
|
+
def create_contents
|
|
153
|
+
return if definition.fetch(:ingredients, []).any?
|
|
144
154
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
Content.create(attributes.merge(element: self))
|
|
155
|
+
definition.fetch("contents", []).each do |attributes|
|
|
156
|
+
Content.create(attributes.merge(element: self))
|
|
157
|
+
end
|
|
149
158
|
end
|
|
150
159
|
end
|
|
151
160
|
end
|
|
@@ -1,112 +1,114 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Alchemy
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
class Element < BaseRecord
|
|
5
|
+
module ElementEssences
|
|
6
|
+
# Returns the contents essence value (aka. ingredient) for passed content name.
|
|
7
|
+
def ingredient(name)
|
|
8
|
+
content = content_by_name(name)
|
|
9
|
+
return nil if content.blank?
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
content.ingredient
|
|
12
|
+
end
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
# True if the element has a content for given name,
|
|
15
|
+
# that has an essence value (aka. ingredient) that is not blank.
|
|
16
|
+
def has_ingredient?(name)
|
|
17
|
+
ingredient(name).present?
|
|
18
|
+
end
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
20
|
+
# Returns all essence errors in the format of:
|
|
21
|
+
#
|
|
22
|
+
# {
|
|
23
|
+
# content.name => [
|
|
24
|
+
# error_message_for_validation_1,
|
|
25
|
+
# error_message_for_validation_2
|
|
26
|
+
# ]
|
|
27
|
+
# }
|
|
28
|
+
#
|
|
29
|
+
# Get translated error messages with +Element#essence_error_messages+
|
|
30
|
+
#
|
|
31
|
+
def essence_errors
|
|
32
|
+
essence_errors = {}
|
|
33
|
+
contents.each do |content|
|
|
34
|
+
if content.essence_validation_failed?
|
|
35
|
+
essence_errors[content.name] = content.essence.validation_errors
|
|
36
|
+
end
|
|
35
37
|
end
|
|
38
|
+
essence_errors
|
|
36
39
|
end
|
|
37
|
-
essence_errors
|
|
38
|
-
end
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
41
|
+
# Essence validation errors
|
|
42
|
+
#
|
|
43
|
+
# == Error messages are translated via I18n
|
|
44
|
+
#
|
|
45
|
+
# Inside your translation file add translations like:
|
|
46
|
+
#
|
|
47
|
+
# alchemy:
|
|
48
|
+
# content_validations:
|
|
49
|
+
# name_of_the_element:
|
|
50
|
+
# name_of_the_content:
|
|
51
|
+
# validation_error_type: Error Message
|
|
52
|
+
#
|
|
53
|
+
# NOTE: +validation_error_type+ has to be one of:
|
|
54
|
+
#
|
|
55
|
+
# * blank
|
|
56
|
+
# * taken
|
|
57
|
+
# * invalid
|
|
58
|
+
#
|
|
59
|
+
# === Example:
|
|
60
|
+
#
|
|
61
|
+
# de:
|
|
62
|
+
# alchemy:
|
|
63
|
+
# content_validations:
|
|
64
|
+
# contactform:
|
|
65
|
+
# email:
|
|
66
|
+
# invalid: 'Die Email hat nicht das richtige Format'
|
|
67
|
+
#
|
|
68
|
+
#
|
|
69
|
+
# == Error message translation fallbacks
|
|
70
|
+
#
|
|
71
|
+
# In order to not translate every single content for every element
|
|
72
|
+
# you can provide default error messages per content name:
|
|
73
|
+
#
|
|
74
|
+
# === Example
|
|
75
|
+
#
|
|
76
|
+
# en:
|
|
77
|
+
# alchemy:
|
|
78
|
+
# content_validations:
|
|
79
|
+
# fields:
|
|
80
|
+
# email:
|
|
81
|
+
# invalid: E-Mail has wrong format
|
|
82
|
+
# blank: E-Mail can't be blank
|
|
83
|
+
#
|
|
84
|
+
# And even further you can provide general field agnostic error messages:
|
|
85
|
+
#
|
|
86
|
+
# === Example
|
|
87
|
+
#
|
|
88
|
+
# en:
|
|
89
|
+
# alchemy:
|
|
90
|
+
# content_validations:
|
|
91
|
+
# errors:
|
|
92
|
+
# invalid: %{field} has wrong format
|
|
93
|
+
# blank: %{field} can't be blank
|
|
94
|
+
#
|
|
95
|
+
def essence_error_messages
|
|
96
|
+
messages = []
|
|
97
|
+
essence_errors.each do |content_name, errors|
|
|
98
|
+
errors.each do |error|
|
|
99
|
+
messages << Alchemy.t(
|
|
100
|
+
"#{name}.#{content_name}.#{error}",
|
|
101
|
+
scope: "content_validations",
|
|
102
|
+
default: [
|
|
103
|
+
"fields.#{content_name}.#{error}".to_sym,
|
|
104
|
+
"errors.#{error}".to_sym,
|
|
105
|
+
],
|
|
106
|
+
field: Content.translated_label_for(content_name, name),
|
|
107
|
+
)
|
|
108
|
+
end
|
|
107
109
|
end
|
|
110
|
+
messages
|
|
108
111
|
end
|
|
109
|
-
messages
|
|
110
112
|
end
|
|
111
113
|
end
|
|
112
114
|
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemy
|
|
4
|
+
class Element < BaseRecord
|
|
5
|
+
# Methods concerning ingredients for elements
|
|
6
|
+
#
|
|
7
|
+
module ElementIngredients
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
attr_accessor :autogenerate_ingredients
|
|
12
|
+
|
|
13
|
+
has_many :ingredients,
|
|
14
|
+
class_name: "Alchemy::Ingredient",
|
|
15
|
+
inverse_of: :element,
|
|
16
|
+
dependent: :destroy
|
|
17
|
+
|
|
18
|
+
before_create :build_ingredients,
|
|
19
|
+
unless: -> { autogenerate_ingredients == false }
|
|
20
|
+
|
|
21
|
+
accepts_nested_attributes_for :ingredients
|
|
22
|
+
validates_associated :ingredients, on: :update
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Find first ingredient from element by given role.
|
|
26
|
+
def ingredient_by_role(role)
|
|
27
|
+
ingredients.detect { |ingredient| ingredient.role == role.to_s }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Find first ingredient from element by given type.
|
|
31
|
+
def ingredient_by_type(type)
|
|
32
|
+
ingredients_by_type(type).first
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# All ingredients from element by given type.
|
|
36
|
+
def ingredients_by_type(type)
|
|
37
|
+
ingredients.select do |ingredient|
|
|
38
|
+
ingredient.type == Ingredient.normalize_type(type)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Copy current ingredient's ingredients to given target element
|
|
43
|
+
def copy_ingredients_to(element)
|
|
44
|
+
ingredients.map do |ingredient|
|
|
45
|
+
Ingredient.copy(ingredient, element_id: element.id)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns all element ingredient definitions from the +elements.yml+ file
|
|
50
|
+
def ingredient_definitions
|
|
51
|
+
definition.fetch(:ingredients, [])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Returns the definition for given ingredient role
|
|
55
|
+
def ingredient_definition_for(role)
|
|
56
|
+
if ingredient_definitions.blank?
|
|
57
|
+
log_warning "Element #{name} is missing the ingredient definition for #{role}"
|
|
58
|
+
nil
|
|
59
|
+
else
|
|
60
|
+
ingredient_definitions.find { |d| d[:role] == role.to_s }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns an array of all Richtext ingredients ids from elements
|
|
65
|
+
#
|
|
66
|
+
# This is used to re-initialize the TinyMCE editor in the element editor.
|
|
67
|
+
#
|
|
68
|
+
def richtext_ingredients_ids
|
|
69
|
+
ids = ingredients.select(&:has_tinymce?).collect(&:id)
|
|
70
|
+
expanded_nested_elements = nested_elements.expanded
|
|
71
|
+
if expanded_nested_elements.present?
|
|
72
|
+
ids += expanded_nested_elements.collect(&:richtext_ingredients_ids)
|
|
73
|
+
end
|
|
74
|
+
ids.flatten
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Has any of the ingredients validations defined?
|
|
78
|
+
def has_validations?
|
|
79
|
+
ingredients.any?(&:has_validations?)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# All element ingredients where the validation has failed.
|
|
83
|
+
def ingredients_with_errors
|
|
84
|
+
ingredients.select { |i| i.errors.any? }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# True if the element has a ingredient for given name
|
|
88
|
+
# that has a non blank value.
|
|
89
|
+
def has_value_for?(role)
|
|
90
|
+
ingredient_by_role(role)&.value.present?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Ingredient validation error messages
|
|
94
|
+
#
|
|
95
|
+
# == Error messages are translated via I18n
|
|
96
|
+
#
|
|
97
|
+
# Inside your translation file add translations like:
|
|
98
|
+
#
|
|
99
|
+
# alchemy:
|
|
100
|
+
# ingredient_validations:
|
|
101
|
+
# name_of_the_element:
|
|
102
|
+
# role_of_the_ingredient:
|
|
103
|
+
# validation_error_type: Error Message
|
|
104
|
+
#
|
|
105
|
+
# NOTE: +validation_error_type+ has to be one of:
|
|
106
|
+
#
|
|
107
|
+
# * blank
|
|
108
|
+
# * taken
|
|
109
|
+
# * invalid
|
|
110
|
+
#
|
|
111
|
+
# === Example:
|
|
112
|
+
#
|
|
113
|
+
# de:
|
|
114
|
+
# alchemy:
|
|
115
|
+
# ingredient_validations:
|
|
116
|
+
# contactform:
|
|
117
|
+
# email:
|
|
118
|
+
# invalid: 'Die Email hat nicht das richtige Format'
|
|
119
|
+
#
|
|
120
|
+
#
|
|
121
|
+
# == Error message translation fallbacks
|
|
122
|
+
#
|
|
123
|
+
# In order to not translate every single ingredient for every element
|
|
124
|
+
# you can provide default error messages per content name:
|
|
125
|
+
#
|
|
126
|
+
# === Example
|
|
127
|
+
#
|
|
128
|
+
# en:
|
|
129
|
+
# alchemy:
|
|
130
|
+
# ingredient_validations:
|
|
131
|
+
# fields:
|
|
132
|
+
# email:
|
|
133
|
+
# invalid: E-Mail has wrong format
|
|
134
|
+
# blank: E-Mail can't be blank
|
|
135
|
+
#
|
|
136
|
+
# And even further you can provide general field agnostic error messages:
|
|
137
|
+
#
|
|
138
|
+
# === Example
|
|
139
|
+
#
|
|
140
|
+
# en:
|
|
141
|
+
# alchemy:
|
|
142
|
+
# ingredient_validations:
|
|
143
|
+
# errors:
|
|
144
|
+
# invalid: %{field} has wrong format
|
|
145
|
+
# blank: %{field} can't be blank
|
|
146
|
+
#
|
|
147
|
+
def ingredient_error_messages
|
|
148
|
+
messages = []
|
|
149
|
+
ingredients_with_errors.map { |i| [i.role, i.errors.details] }.each do |role, error_details|
|
|
150
|
+
error_details[:value].each do |error_detail|
|
|
151
|
+
error = error_detail[:error]
|
|
152
|
+
messages << Alchemy.t(
|
|
153
|
+
"#{name}.#{role}.#{error}",
|
|
154
|
+
scope: "ingredient_validations",
|
|
155
|
+
default: [
|
|
156
|
+
"fields.#{role}.#{error}".to_sym,
|
|
157
|
+
"errors.#{error}".to_sym,
|
|
158
|
+
],
|
|
159
|
+
field: Alchemy::Ingredient.translated_label_for(role, name),
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
messages
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
# Builds ingredients for this element as described in the +elements.yml+
|
|
169
|
+
def build_ingredients
|
|
170
|
+
self.ingredients = ingredient_definitions.map do |attributes|
|
|
171
|
+
Ingredient.build(role: attributes[:role], element: self)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|