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,237 +1,218 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Alchemy
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
inverse_of: :page
|
|
22
|
-
has_many :fixed_elements,
|
|
23
|
-
-> { order(:position).fixed.available },
|
|
24
|
-
class_name: "Alchemy::Element",
|
|
25
|
-
inverse_of: :page
|
|
26
|
-
has_many :dependent_destroyable_elements,
|
|
27
|
-
-> { not_nested },
|
|
28
|
-
class_name: "Alchemy::Element",
|
|
29
|
-
dependent: :destroy
|
|
30
|
-
has_many :contents, through: :elements
|
|
31
|
-
has_and_belongs_to_many :to_be_swept_elements, -> { distinct },
|
|
32
|
-
class_name: "Alchemy::Element",
|
|
33
|
-
join_table: ElementToPage.table_name
|
|
34
|
-
|
|
35
|
-
after_create :generate_elements,
|
|
36
|
-
unless: -> { autogenerate_elements == false }
|
|
37
|
-
|
|
38
|
-
after_update :trash_not_allowed_elements!,
|
|
39
|
-
if: :saved_change_to_page_layout?
|
|
40
|
-
|
|
41
|
-
after_update(if: :saved_change_to_page_layout?) do
|
|
42
|
-
Alchemy::Deprecation.warn(
|
|
43
|
-
"Autogenerating elements on page_layout change is deprecated and will be removed from Alchemy 6.0"
|
|
44
|
-
)
|
|
45
|
-
generate_elements
|
|
46
|
-
end
|
|
47
|
-
end
|
|
4
|
+
class Page < BaseRecord
|
|
5
|
+
module PageElements
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
attr_accessor :autogenerate_elements
|
|
10
|
+
|
|
11
|
+
with_options(
|
|
12
|
+
class_name: "Alchemy::Element",
|
|
13
|
+
through: :public_version,
|
|
14
|
+
inverse_of: :page,
|
|
15
|
+
source: :elements,
|
|
16
|
+
) do
|
|
17
|
+
has_many :all_elements
|
|
18
|
+
has_many :elements, -> { not_nested.unfixed.available }
|
|
19
|
+
has_many :fixed_elements, -> { fixed.available }
|
|
20
|
+
end
|
|
48
21
|
|
|
49
|
-
|
|
50
|
-
|
|
22
|
+
has_many :contents, through: :elements
|
|
23
|
+
has_and_belongs_to_many :to_be_swept_elements, -> { distinct },
|
|
24
|
+
class_name: "Alchemy::Element",
|
|
25
|
+
join_table: ElementToPage.table_name
|
|
51
26
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
# @param source [Alchemy::Page]
|
|
55
|
-
# @param target [Alchemy::Page]
|
|
56
|
-
# @return [Array]
|
|
57
|
-
#
|
|
58
|
-
def copy_elements(source, target)
|
|
59
|
-
source_elements = source.all_elements.not_nested.not_trashed
|
|
60
|
-
source_elements.order(:position).map do |source_element|
|
|
61
|
-
Element.copy(source_element, {
|
|
62
|
-
page_id: target.id,
|
|
63
|
-
}).tap(&:move_to_bottom)
|
|
64
|
-
end
|
|
27
|
+
after_create :generate_elements,
|
|
28
|
+
unless: -> { autogenerate_elements == false }
|
|
65
29
|
end
|
|
66
|
-
end
|
|
67
30
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
#
|
|
88
|
-
def available_element_definitions(only_element_named = nil)
|
|
89
|
-
@_element_definitions ||= if only_element_named
|
|
90
|
-
definition = Element.definition_by_name(only_element_named)
|
|
91
|
-
element_definitions_by_name(definition["nestable_elements"])
|
|
92
|
-
else
|
|
93
|
-
element_definitions
|
|
31
|
+
module ClassMethods
|
|
32
|
+
# Copy page elements
|
|
33
|
+
#
|
|
34
|
+
# @param source [Alchemy::Page]
|
|
35
|
+
# @param target [Alchemy::Page]
|
|
36
|
+
# @return [Array]
|
|
37
|
+
#
|
|
38
|
+
def copy_elements(source, target)
|
|
39
|
+
repository = source.draft_version.element_repository
|
|
40
|
+
transaction do
|
|
41
|
+
Element.acts_as_list_no_update do
|
|
42
|
+
repository.not_nested.each.with_index(1) do |element, position|
|
|
43
|
+
Alchemy::DuplicateElement.new(element, repository: repository).call(
|
|
44
|
+
page_version_id: target.draft_version.id,
|
|
45
|
+
position: position,
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
94
50
|
end
|
|
51
|
+
end
|
|
95
52
|
|
|
96
|
-
|
|
53
|
+
# All available element definitions that can actually be placed on current page.
|
|
54
|
+
#
|
|
55
|
+
# It extracts all definitions that are unique or limited and already on page.
|
|
56
|
+
#
|
|
57
|
+
# == Example of unique element:
|
|
58
|
+
#
|
|
59
|
+
# - name: headline
|
|
60
|
+
# unique: true
|
|
61
|
+
# contents:
|
|
62
|
+
# - name: headline
|
|
63
|
+
# type: EssenceText
|
|
64
|
+
#
|
|
65
|
+
# == Example of limited element:
|
|
66
|
+
#
|
|
67
|
+
# - name: article
|
|
68
|
+
# amount: 2
|
|
69
|
+
# contents:
|
|
70
|
+
# - name: text
|
|
71
|
+
# type: EssenceRichtext
|
|
72
|
+
#
|
|
73
|
+
def available_element_definitions(only_element_named = nil)
|
|
74
|
+
@_element_definitions ||= if only_element_named
|
|
75
|
+
definition = Element.definition_by_name(only_element_named)
|
|
76
|
+
element_definitions_by_name(definition["nestable_elements"])
|
|
77
|
+
else
|
|
78
|
+
element_definitions
|
|
79
|
+
end
|
|
97
80
|
|
|
98
|
-
|
|
99
|
-
@_existing_element_names = existing_elements.pluck(:name)
|
|
100
|
-
delete_unique_element_definitions!
|
|
101
|
-
delete_outnumbered_element_definitions!
|
|
81
|
+
return [] if @_element_definitions.blank?
|
|
102
82
|
|
|
103
|
-
|
|
104
|
-
|
|
83
|
+
existing_elements = draft_version.elements.not_nested
|
|
84
|
+
@_existing_element_names = existing_elements.pluck(:name)
|
|
85
|
+
delete_unique_element_definitions!
|
|
86
|
+
delete_outnumbered_element_definitions!
|
|
105
87
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def available_element_names
|
|
109
|
-
@_available_element_names ||= available_element_definitions.map { |e| e["name"] }
|
|
110
|
-
end
|
|
88
|
+
@_element_definitions
|
|
89
|
+
end
|
|
111
90
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
available_element_definitions(parent.name).reject do |e|
|
|
118
|
-
parents_unique_nested_elements.include? e["name"]
|
|
119
|
-
end
|
|
120
|
-
else
|
|
121
|
-
available_element_definitions
|
|
122
|
-
end
|
|
123
|
-
end
|
|
91
|
+
# All names of elements that can actually be placed on current page.
|
|
92
|
+
#
|
|
93
|
+
def available_element_names
|
|
94
|
+
@_available_element_names ||= available_element_definitions.map { |e| e["name"] }
|
|
95
|
+
end
|
|
124
96
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
97
|
+
# Available element definitions excluding nested unique elements.
|
|
98
|
+
#
|
|
99
|
+
def available_elements_within_current_scope(parent)
|
|
100
|
+
@_available_elements = if parent
|
|
101
|
+
parents_unique_nested_elements = parent.nested_elements.where(unique: true).pluck(:name)
|
|
102
|
+
available_element_definitions(parent.name).reject do |e|
|
|
103
|
+
parents_unique_nested_elements.include? e["name"]
|
|
104
|
+
end
|
|
105
|
+
else
|
|
106
|
+
available_element_definitions
|
|
107
|
+
end
|
|
108
|
+
end
|
|
133
109
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
110
|
+
# All element definitions defined for page's page layout
|
|
111
|
+
#
|
|
112
|
+
# Warning: Since elements can be unique or limited in number,
|
|
113
|
+
# it is more safe to ask for +available_element_definitions+
|
|
114
|
+
#
|
|
115
|
+
def element_definitions
|
|
116
|
+
@_element_definitions ||= element_definitions_by_name(element_definition_names)
|
|
140
117
|
end
|
|
141
|
-
definitions.uniq { |d| d["name"] }
|
|
142
|
-
end
|
|
143
118
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def element_definition_names
|
|
154
|
-
definition["elements"] || []
|
|
155
|
-
end
|
|
119
|
+
# All element definitions defined for page's page layout including nestable element definitions
|
|
120
|
+
#
|
|
121
|
+
def descendent_element_definitions
|
|
122
|
+
definitions = element_definitions_by_name(element_definition_names)
|
|
123
|
+
definitions.select { |d| d.key?("nestable_elements") }.each do |d|
|
|
124
|
+
definitions += element_definitions_by_name(d["nestable_elements"])
|
|
125
|
+
end
|
|
126
|
+
definitions.uniq { |d| d["name"] }
|
|
127
|
+
end
|
|
156
128
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
Element.definitions
|
|
169
|
-
else
|
|
170
|
-
Element.definitions.select { |e| names.include? e["name"] }
|
|
129
|
+
# All names of elements that are defined in the page definition.
|
|
130
|
+
#
|
|
131
|
+
# Assign elements to a page in +config/alchemy/page_layouts.yml+.
|
|
132
|
+
#
|
|
133
|
+
# == Example of page_layouts.yml:
|
|
134
|
+
#
|
|
135
|
+
# - name: contact
|
|
136
|
+
# elements: [headline, contactform]
|
|
137
|
+
#
|
|
138
|
+
def element_definition_names
|
|
139
|
+
definition["elements"] || []
|
|
171
140
|
end
|
|
172
|
-
end
|
|
173
141
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
elements.named(definition["feed_elements"])
|
|
184
|
-
end
|
|
142
|
+
# Element definitions with given name(s)
|
|
143
|
+
#
|
|
144
|
+
# @param [Array || String]
|
|
145
|
+
# one or many Alchemy::Element names. Pass +'all'+ to get all Element definitions
|
|
146
|
+
# @return [Array]
|
|
147
|
+
# An Array of element definitions
|
|
148
|
+
#
|
|
149
|
+
def element_definitions_by_name(names)
|
|
150
|
+
return [] if names.blank?
|
|
185
151
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
.collect(&:id)
|
|
193
|
-
end
|
|
152
|
+
if names.to_s == "all"
|
|
153
|
+
Element.definitions
|
|
154
|
+
else
|
|
155
|
+
Element.definitions.select { |e| names.include? e["name"] }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
194
158
|
|
|
195
|
-
|
|
159
|
+
# Returns all elements that should be feeded via rss.
|
|
160
|
+
#
|
|
161
|
+
# Define feedable elements in your +page_layouts.yml+:
|
|
162
|
+
#
|
|
163
|
+
# - name: news
|
|
164
|
+
# feed: true
|
|
165
|
+
# feed_elements: [element_name, element_2_name]
|
|
166
|
+
#
|
|
167
|
+
def feed_elements
|
|
168
|
+
elements.named(definition["feed_elements"])
|
|
169
|
+
end
|
|
196
170
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
next if existing_element_names.include?(element_name)
|
|
171
|
+
# Returns an array of all EssenceRichtext contents ids from not folded elements
|
|
172
|
+
#
|
|
173
|
+
def richtext_contents_ids
|
|
174
|
+
Alchemy::Content.joins(:element)
|
|
175
|
+
.where(Element.table_name => { page_version_id: draft_version.id, folded: false })
|
|
176
|
+
.select(&:has_tinymce?)
|
|
177
|
+
.collect(&:id)
|
|
178
|
+
end
|
|
206
179
|
|
|
207
|
-
|
|
180
|
+
# Returns an array of all Richtext ingredients ids from not folded elements
|
|
181
|
+
#
|
|
182
|
+
def richtext_ingredients_ids
|
|
183
|
+
Alchemy::Ingredient.richtexts.joins(:element)
|
|
184
|
+
.where(Element.table_name => { page_version_id: draft_version.id, folded: false })
|
|
185
|
+
.select(&:has_tinymce?)
|
|
186
|
+
.collect(&:id)
|
|
208
187
|
end
|
|
209
|
-
end
|
|
210
188
|
|
|
211
|
-
|
|
212
|
-
def trash_not_allowed_elements!
|
|
213
|
-
not_allowed_elements = elements.where([
|
|
214
|
-
"#{Element.table_name}.name NOT IN (?)",
|
|
215
|
-
element_definition_names,
|
|
216
|
-
])
|
|
217
|
-
not_allowed_elements.to_a.map(&:trash!)
|
|
218
|
-
end
|
|
219
|
-
deprecate :trash_not_allowed_elements!, deprecator: Alchemy::Deprecation
|
|
189
|
+
private
|
|
220
190
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
191
|
+
# Looks in the page_layout descripion, if there are elements to autogenerate.
|
|
192
|
+
#
|
|
193
|
+
# And if so, it generates them.
|
|
194
|
+
#
|
|
195
|
+
def generate_elements
|
|
196
|
+
definition.fetch("autogenerate", []).each do |element_name|
|
|
197
|
+
Element.create(page: self, page_version: draft_version, name: element_name)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Deletes unique and already present definitions from @_element_definitions.
|
|
202
|
+
#
|
|
203
|
+
def delete_unique_element_definitions!
|
|
204
|
+
@_element_definitions.delete_if do |element|
|
|
205
|
+
element["unique"] && @_existing_element_names.include?(element["name"])
|
|
206
|
+
end
|
|
226
207
|
end
|
|
227
|
-
end
|
|
228
208
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
209
|
+
# Deletes limited and outnumbered definitions from @_element_definitions.
|
|
210
|
+
#
|
|
211
|
+
def delete_outnumbered_element_definitions!
|
|
212
|
+
@_element_definitions.delete_if do |element|
|
|
213
|
+
outnumbered = @_existing_element_names.select { |name| name == element["name"] }
|
|
214
|
+
element["amount"] && outnumbered.count >= element["amount"].to_i
|
|
215
|
+
end
|
|
235
216
|
end
|
|
236
217
|
end
|
|
237
218
|
end
|
|
@@ -1,85 +1,87 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Alchemy
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
class Page < BaseRecord
|
|
5
|
+
module PageNaming
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
include NameConversions
|
|
8
|
+
RESERVED_URLNAMES = %w(admin messages new)
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
included do
|
|
11
|
+
before_validation :set_urlname,
|
|
12
|
+
if: :renamed?,
|
|
13
|
+
unless: -> { name.blank? }
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
validates :name,
|
|
16
|
+
presence: true
|
|
17
|
+
validates :urlname,
|
|
18
|
+
uniqueness: { scope: [:language_id, :layoutpage], if: -> { urlname.present? } },
|
|
19
|
+
exclusion: { in: RESERVED_URLNAMES },
|
|
20
|
+
length: { minimum: 3, if: -> { urlname.present? } }
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
before_save :set_title,
|
|
23
|
+
if: -> { title.blank? }
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
after_update :update_descendants_urlnames,
|
|
26
|
+
if: :saved_change_to_urlname?
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
after_move :update_urlname!
|
|
29
|
+
end
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
# Returns true if name or urlname has changed.
|
|
32
|
+
def renamed?
|
|
33
|
+
name_changed? || urlname_changed?
|
|
34
|
+
end
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
# Makes a slug of all ancestors urlnames including mine and delimit them be slash.
|
|
37
|
+
# So the whole path is stored as urlname in the database.
|
|
38
|
+
def update_urlname!
|
|
39
|
+
new_urlname = nested_url_name
|
|
40
|
+
if urlname != new_urlname
|
|
41
|
+
legacy_urls.create(urlname: urlname)
|
|
42
|
+
update_column(:urlname, new_urlname)
|
|
43
|
+
end
|
|
42
44
|
end
|
|
43
|
-
end
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
# Returns always the last part of a urlname path
|
|
47
|
+
def slug
|
|
48
|
+
urlname.to_s.split("/").last
|
|
49
|
+
end
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
private
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
def update_descendants_urlnames
|
|
54
|
+
reload
|
|
55
|
+
descendants.each(&:update_urlname!)
|
|
56
|
+
end
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
# Sets the urlname to a url friendly slug.
|
|
59
|
+
# Either from name, or if present, from urlname.
|
|
60
|
+
# The urlname contains the whole path including parent urlnames.
|
|
61
|
+
def set_urlname
|
|
62
|
+
self[:urlname] = nested_url_name
|
|
63
|
+
end
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
def set_title
|
|
66
|
+
self[:title] = name
|
|
67
|
+
end
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
# Converts the given name into an url friendly string.
|
|
70
|
+
#
|
|
71
|
+
# Names shorter than 3 will be filled up with dashes,
|
|
72
|
+
# so it does not collidate with the language code.
|
|
73
|
+
#
|
|
74
|
+
def converted_url_name
|
|
75
|
+
url_name = convert_to_urlname(slug.blank? ? name : slug)
|
|
76
|
+
url_name.rjust(3, "-")
|
|
77
|
+
end
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
def nested_url_name
|
|
80
|
+
if parent&.language_root?
|
|
81
|
+
converted_url_name
|
|
82
|
+
else
|
|
83
|
+
[parent&.urlname, converted_url_name].compact.join("/")
|
|
84
|
+
end
|
|
83
85
|
end
|
|
84
86
|
end
|
|
85
87
|
end
|