alchemy_cms 8.0.11 → 8.1.0
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/README.md +14 -10
- data/app/assets/builds/alchemy/admin.css +1 -1
- data/app/assets/builds/alchemy/dark-theme.css +1 -1
- data/app/assets/builds/alchemy/light-theme.css +1 -1
- data/app/assets/builds/alchemy/preview.min.js +1 -1
- data/app/assets/builds/alchemy/theme.css +1 -1
- data/app/{views/alchemy/admin/elements/_element.html.erb → components/alchemy/admin/element_editor.html.erb} +34 -29
- data/app/components/alchemy/admin/element_editor.rb +115 -0
- data/app/components/alchemy/admin/element_select.rb +12 -9
- data/app/components/alchemy/admin/ingredient_editor.rb +54 -0
- data/app/components/alchemy/admin/list_filter.rb +16 -5
- data/app/components/alchemy/admin/page_node.html.erb +214 -0
- data/app/components/alchemy/admin/page_node.rb +70 -0
- data/app/components/alchemy/admin/picture_thumbnail.rb +36 -0
- data/app/components/alchemy/admin/publish_page_button.html.erb +15 -0
- data/app/components/alchemy/admin/publish_page_button.rb +54 -0
- data/app/{helpers/alchemy/admin/tags_helper.rb → components/alchemy/admin/tags_list.rb} +19 -11
- data/app/components/alchemy/admin/toolbar_button.rb +17 -13
- data/app/components/alchemy/ingredients/audio_editor.rb +8 -0
- data/app/components/alchemy/ingredients/base_editor.rb +222 -0
- data/app/components/alchemy/ingredients/boolean_editor.rb +21 -0
- data/app/components/alchemy/ingredients/color_editor.rb +80 -0
- data/app/components/alchemy/ingredients/color_view.rb +13 -0
- data/app/components/alchemy/ingredients/datetime_editor.rb +28 -0
- data/app/components/alchemy/ingredients/file_editor.rb +69 -0
- data/app/components/alchemy/ingredients/headline_editor.rb +88 -0
- data/app/components/alchemy/ingredients/html_editor.rb +11 -0
- data/app/components/alchemy/ingredients/link_editor.rb +29 -0
- data/app/components/alchemy/ingredients/node_editor.rb +23 -0
- data/app/components/alchemy/ingredients/number_editor.rb +28 -0
- data/app/components/alchemy/ingredients/page_editor.rb +19 -0
- data/app/components/alchemy/ingredients/picture_editor.rb +81 -0
- data/app/components/alchemy/ingredients/richtext_editor.rb +31 -0
- data/app/components/alchemy/ingredients/select_editor.rb +37 -0
- data/app/components/alchemy/ingredients/select_view.rb +7 -0
- data/app/components/alchemy/ingredients/text_editor.rb +41 -0
- data/app/components/alchemy/ingredients/video_editor.rb +8 -0
- data/app/controllers/alchemy/admin/attachments_controller.rb +8 -6
- data/app/controllers/alchemy/admin/base_controller.rb +7 -18
- data/app/controllers/alchemy/admin/clipboard_controller.rb +15 -11
- data/app/controllers/alchemy/admin/dashboard_controller.rb +2 -2
- data/app/controllers/alchemy/admin/elements_controller.rb +34 -32
- data/app/controllers/alchemy/admin/ingredients_controller.rb +1 -0
- data/app/controllers/alchemy/admin/languages_controller.rb +0 -3
- data/app/controllers/alchemy/admin/layoutpages_controller.rb +2 -1
- data/app/controllers/alchemy/admin/legacy_page_urls_controller.rb +1 -1
- data/app/controllers/alchemy/admin/nodes_controller.rb +24 -1
- data/app/controllers/alchemy/admin/pages_controller.rb +36 -42
- data/app/controllers/alchemy/admin/pictures_controller.rb +2 -5
- data/app/controllers/alchemy/admin/resources_controller.rb +1 -1
- data/app/controllers/alchemy/api/ingredients_controller.rb +1 -1
- data/app/controllers/alchemy/api/pages_controller.rb +5 -3
- data/app/controllers/alchemy/base_controller.rb +6 -6
- data/app/controllers/alchemy/pages_controller.rb +12 -6
- data/app/controllers/concerns/alchemy/admin/archive_overlay.rb +0 -1
- data/app/controllers/concerns/alchemy/admin/clipboard.rb +57 -0
- data/app/controllers/concerns/alchemy/admin/uploader_responses.rb +2 -2
- data/app/controllers/concerns/alchemy/site_redirects.rb +1 -1
- data/app/decorators/alchemy/ingredient_editor.rb +37 -4
- data/app/helpers/alchemy/admin/base_helper.rb +10 -6
- data/app/helpers/alchemy/admin/ingredients_helper.rb +6 -3
- data/app/helpers/alchemy/base_helper.rb +1 -1
- data/app/helpers/alchemy/pages_helper.rb +1 -1
- data/app/javascript/alchemy_admin/components/action.js +5 -1
- data/app/javascript/alchemy_admin/components/color_select.js +73 -0
- data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +11 -3
- data/app/javascript/alchemy_admin/components/element_editor/publish_element_button.js +7 -2
- data/app/javascript/alchemy_admin/components/element_editor.js +11 -12
- data/app/javascript/alchemy_admin/components/element_select.js +39 -17
- data/app/javascript/alchemy_admin/components/elements_window.js +0 -2
- data/app/javascript/alchemy_admin/components/file_editor.js +26 -0
- data/app/javascript/alchemy_admin/components/index.js +9 -0
- data/app/javascript/alchemy_admin/components/list_filter.js +57 -8
- data/app/javascript/alchemy_admin/components/message.js +9 -3
- data/app/javascript/alchemy_admin/components/page_node.js +119 -0
- data/app/javascript/alchemy_admin/{page_publication_fields.js → components/page_publication_fields.js} +9 -8
- data/app/javascript/alchemy_admin/{picture_editors.js → components/picture_editor.js} +30 -45
- data/app/javascript/alchemy_admin/components/picture_thumbnail.js +107 -0
- data/app/javascript/alchemy_admin/components/publish_page_button.js +41 -0
- data/app/javascript/alchemy_admin/components/select.js +3 -1
- data/app/javascript/alchemy_admin/components/sitemap.js +210 -0
- data/app/javascript/alchemy_admin/{sortable_elements.js → components/sortable_elements.js} +22 -25
- data/app/javascript/alchemy_admin/components/tinymce.js +10 -5
- data/app/javascript/alchemy_admin/components/uploader.js +30 -0
- data/app/javascript/alchemy_admin/image_overlay.js +0 -2
- data/app/javascript/alchemy_admin/initializer.js +0 -3
- data/app/javascript/alchemy_admin/link_dialog.js +1 -6
- data/app/javascript/alchemy_admin/templates/compiled.js +1 -1
- data/app/javascript/alchemy_admin/utils/ajax.js +15 -3
- data/app/javascript/alchemy_admin.js +0 -6
- data/app/models/alchemy/attachment.rb +4 -4
- data/app/models/alchemy/element/definitions.rb +1 -2
- data/app/models/alchemy/element/element_ingredients.rb +6 -2
- data/app/models/alchemy/element.rb +54 -13
- data/app/models/alchemy/element_definition.rb +4 -1
- data/app/models/alchemy/elements_repository.rb +6 -0
- data/app/models/alchemy/folded_page.rb +2 -2
- data/app/models/alchemy/ingredient.rb +38 -1
- data/app/models/alchemy/ingredient_definition.rb +4 -1
- data/app/models/alchemy/ingredient_validator.rb +6 -2
- data/app/models/alchemy/ingredients/color.rb +10 -0
- data/app/models/alchemy/ingredients/headline.rb +2 -17
- data/app/models/alchemy/ingredients/picture.rb +4 -4
- data/app/models/alchemy/ingredients/select.rb +19 -0
- data/app/models/alchemy/language/code.rb +0 -1
- data/app/models/alchemy/node.rb +28 -1
- data/app/models/alchemy/page/page_naming.rb +0 -7
- data/app/models/alchemy/page/page_natures.rb +7 -3
- data/app/models/alchemy/page/page_scopes.rb +13 -1
- data/app/models/alchemy/page/publisher.rb +14 -2
- data/app/models/alchemy/page.rb +102 -23
- data/app/models/alchemy/page_definition.rb +4 -1
- data/app/models/alchemy/page_version.rb +22 -6
- data/app/models/alchemy/picture.rb +10 -11
- data/app/models/alchemy/picture_variant.rb +1 -3
- data/app/models/alchemy/resource.rb +1 -1
- data/app/models/alchemy/storage_adapter/active_storage.rb +14 -2
- data/app/models/alchemy/storage_adapter/dragonfly.rb +12 -0
- data/app/models/alchemy/storage_adapter.rb +2 -0
- data/app/models/concerns/alchemy/picture_thumbnails.rb +4 -4
- data/app/models/concerns/alchemy/publishable.rb +54 -0
- data/app/serializers/alchemy/page_tree_serializer.rb +11 -31
- data/app/services/alchemy/copy_page.rb +17 -0
- data/app/services/alchemy/duplicate_element.rb +1 -1
- data/app/services/alchemy/page_tree_preloader.rb +105 -0
- data/app/stylesheets/alchemy/_extends.scss +3 -9
- data/app/stylesheets/alchemy/_mixins.scss +3 -1
- data/app/stylesheets/alchemy/_themes.scss +19 -10
- data/app/stylesheets/alchemy/admin/archive.scss +1 -0
- data/app/stylesheets/alchemy/admin/base.scss +5 -2
- data/app/stylesheets/alchemy/admin/buttons.scss +3 -3
- data/app/stylesheets/alchemy/admin/element-select.scss +18 -0
- data/app/stylesheets/alchemy/admin/elements.scss +123 -23
- data/app/stylesheets/alchemy/admin/errors.scss +1 -1
- data/app/stylesheets/alchemy/admin/flash.scss +6 -4
- data/app/stylesheets/alchemy/admin/images.scss +9 -5
- data/app/stylesheets/alchemy/admin/list_filter.scss +4 -4
- data/app/stylesheets/alchemy/admin/navigation.scss +1 -1
- data/app/stylesheets/alchemy/admin/notices.scss +1 -2
- data/app/stylesheets/alchemy/admin/selects.scss +36 -21
- data/app/stylesheets/alchemy/admin/shoelace.scss +14 -1
- data/app/stylesheets/alchemy/admin/sitemap.scss +11 -3
- data/app/stylesheets/alchemy/admin/tags.scss +3 -1
- data/app/stylesheets/alchemy/admin/toolbar.scss +1 -1
- data/app/views/alchemy/_edit_mode.html.erb +1 -1
- data/app/views/alchemy/_menubar.html.erb +1 -1
- data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +35 -31
- data/app/views/alchemy/admin/attachments/_library_sidebar.html.erb +6 -0
- data/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb +1 -1
- data/app/views/alchemy/admin/attachments/_replace_button.html.erb +1 -8
- data/app/views/alchemy/admin/attachments/_sorting_select.html.erb +13 -0
- data/app/views/alchemy/admin/attachments/_tag_list.html.erb +2 -3
- data/app/views/alchemy/admin/attachments/index.html.erb +5 -11
- data/app/views/alchemy/admin/attachments/show.html.erb +1 -1
- data/app/views/alchemy/admin/clipboard/_button.html.erb +1 -0
- data/app/views/alchemy/admin/clipboard/index.html.erb +4 -5
- data/app/views/alchemy/admin/clipboard/insert.turbo_stream.erb +1 -1
- data/app/views/alchemy/admin/crop.html.erb +5 -7
- data/app/views/alchemy/admin/dashboard/widgets/_locked_pages.html.erb +1 -1
- data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +6 -6
- data/app/views/alchemy/admin/elements/_fixed_element.html.erb +1 -1
- data/app/views/alchemy/admin/elements/_footer.html.erb +7 -1
- data/app/views/alchemy/admin/elements/_header.html.erb +5 -5
- data/app/views/alchemy/admin/elements/_toolbar.html.erb +33 -8
- data/app/views/alchemy/admin/elements/create.turbo_stream.erb +10 -10
- data/app/views/alchemy/admin/elements/index.html.erb +29 -16
- data/app/views/alchemy/admin/elements/new.html.erb +2 -2
- data/app/views/alchemy/admin/ingredients/update.turbo_stream.erb +3 -5
- data/app/views/alchemy/admin/leave.html.erb +1 -1
- data/app/views/alchemy/admin/nodes/_node.html.erb +19 -0
- data/app/views/alchemy/admin/nodes/edit.html.erb +1 -1
- data/app/views/alchemy/admin/nodes/index.html.erb +3 -1
- data/app/views/alchemy/admin/nodes/new.html.erb +14 -1
- data/app/views/alchemy/admin/pages/_current_page.html.erb +3 -1
- data/app/views/alchemy/admin/pages/_form.html.erb +21 -9
- data/app/views/alchemy/admin/pages/_page_status.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_publication_fields.html.erb +28 -26
- data/app/views/alchemy/admin/pages/_table.html.erb +0 -7
- data/app/views/alchemy/admin/pages/_toolbar.html.erb +3 -6
- data/app/views/alchemy/admin/pages/edit.html.erb +5 -11
- data/app/views/alchemy/admin/pages/flush.turbo_stream.erb +2 -0
- data/app/views/alchemy/admin/pages/fold.turbo_stream.erb +5 -0
- data/app/views/alchemy/admin/pages/index.html.erb +5 -3
- data/app/views/alchemy/admin/pages/new.html.erb +2 -12
- data/app/views/alchemy/admin/pages/publish.turbo_stream.erb +12 -0
- data/app/views/alchemy/admin/pages/tree.html.erb +13 -0
- data/app/views/alchemy/admin/pages/update.turbo_stream.erb +5 -16
- data/app/views/alchemy/admin/partials/_flash_notices.html.erb +1 -1
- data/app/views/alchemy/admin/partials/{_remote_search_form.html.erb → _overlay_search_form.html.erb} +1 -2
- data/app/views/alchemy/admin/partials/_paste_from_clipboard_form.html.erb +12 -0
- data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +24 -21
- data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +18 -26
- data/app/views/alchemy/admin/pictures/_picture.html.erb +11 -15
- data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +3 -6
- data/app/views/alchemy/admin/pictures/_tag_list.html.erb +2 -3
- data/app/views/alchemy/admin/pictures/index.html.erb +0 -1
- data/app/views/alchemy/admin/pictures/update.turbo_stream.erb +1 -1
- data/app/views/alchemy/admin/resources/_resource_usage_info.html.erb +1 -1
- data/app/views/alchemy/admin/resources/_tag_list.html.erb +2 -3
- data/app/views/alchemy/admin/styleguide/index.html.erb +25 -20
- data/app/views/alchemy/admin/tags/edit.html.erb +1 -1
- data/app/views/alchemy/admin/tinymce/_setup.html.erb +2 -2
- data/app/views/alchemy/admin/uploader/_button.html.erb +1 -15
- data/app/views/alchemy/attachments/show.html.erb +1 -1
- data/app/views/alchemy/base/permission_denied.js.erb +1 -1
- data/app/views/alchemy/ingredients/shared/_anchor.html.erb +9 -7
- data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +12 -5
- data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +10 -11
- data/app/views/alchemy/language_links/_spacer.html.erb +1 -1
- data/app/views/alchemy/messages_mailer/new.html.erb +1 -1
- data/app/views/alchemy/welcome.html.erb +1 -1
- data/config/locales/alchemy.en.yml +12 -3
- data/config/routes.rb +2 -2
- data/db/migrate/20230123112425_add_searchable_to_alchemy_pages.rb +1 -1
- data/db/migrate/20230505132743_add_indexes_to_alchemy_pictures.rb +1 -1
- data/db/migrate/20231113104432_create_page_mutexes.rb +1 -1
- data/db/migrate/20240314105244_create_alchemy_picture_descriptions.rb +1 -1
- data/db/migrate/20250626160259_add_unique_index_to_picture_descriptions.rb +1 -1
- data/db/migrate/20250905140323_add_created_at_index_to_pictures_and_attachments.rb +1 -1
- data/db/migrate/20251106150010_convert_select_value_for_multiple.rb +11 -0
- data/db/migrate/20260102121232_add_metadata_to_page_versions.rb +9 -0
- data/db/migrate/20260115164704_add_publication_timestamps_to_alchemy_elements.rb +30 -0
- data/db/migrate/20260115164705_add_index_to_element_publication_timestamps.rb +13 -0
- data/lib/alchemy/ability_helper.rb +1 -3
- data/lib/alchemy/auth_accessors.rb +51 -117
- data/lib/alchemy/configuration.rb +1 -0
- data/lib/alchemy/configurations/main.rb +63 -0
- data/lib/alchemy/controller_actions.rb +2 -3
- data/lib/alchemy/engine.rb +9 -12
- data/lib/alchemy/error_tracking/error_logger.rb +1 -1
- data/lib/alchemy/errors.rb +1 -1
- data/lib/alchemy/logger.rb +34 -4
- data/lib/alchemy/name_conversions.rb +0 -6
- data/lib/alchemy/seeder.rb +2 -2
- data/lib/alchemy/tasks/usage.rb +4 -4
- data/lib/alchemy/test_support/factories/page_version_factory.rb +3 -0
- data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +30 -0
- data/lib/alchemy/test_support/shared_ingredient_editor_examples.rb +26 -6
- data/lib/alchemy/test_support/shared_publishable_examples.rb +114 -0
- data/lib/alchemy/upgrader/eight_one.rb +56 -0
- data/lib/alchemy/upgrader.rb +9 -1
- data/lib/alchemy/version.rb +1 -1
- data/lib/alchemy.rb +1 -4
- data/lib/alchemy_cms.rb +0 -1
- data/lib/generators/alchemy/elements/templates/view.html.erb +3 -3
- data/lib/generators/alchemy/ingredient/ingredient_generator.rb +6 -8
- data/lib/generators/alchemy/ingredient/templates/editor_component.rb.tt +22 -0
- data/lib/generators/alchemy/page_layouts/templates/layout.html.erb +1 -1
- data/lib/generators/alchemy/site_layouts/templates/layout.html.erb +1 -1
- data/lib/tasks/alchemy/upgrade.rake +21 -7
- data/vendor/javascript/shoelace.min.js +713 -31
- data/vendor/javascript/tinymce.min.js +1 -1
- metadata +104 -84
- data/app/decorators/alchemy/element_editor.rb +0 -90
- data/app/helpers/alchemy/admin/pictures_helper.rb +0 -14
- data/app/javascript/alchemy_admin/file_editors.js +0 -28
- data/app/javascript/alchemy_admin/image_loader.js +0 -54
- data/app/javascript/alchemy_admin/page_sorter.js +0 -71
- data/app/javascript/alchemy_admin/sitemap.js +0 -154
- data/app/javascript/alchemy_admin/templates/page_folder.hbs +0 -3
- data/app/views/alchemy/admin/attachments/archive_overlay.js.erb +0 -4
- data/app/views/alchemy/admin/pages/_page.html.erb +0 -163
- data/app/views/alchemy/admin/pages/_sitemap.html.erb +0 -30
- data/app/views/alchemy/admin/pages/flush.js.erb +0 -2
- data/app/views/alchemy/admin/pictures/archive_overlay.js.erb +0 -5
- data/app/views/alchemy/admin/pictures/index.js.erb +0 -2
- data/app/views/alchemy/ingredients/_audio_editor.html.erb +0 -5
- data/app/views/alchemy/ingredients/_boolean_editor.html.erb +0 -11
- data/app/views/alchemy/ingredients/_datetime_editor.html.erb +0 -20
- data/app/views/alchemy/ingredients/_file_editor.html.erb +0 -52
- data/app/views/alchemy/ingredients/_headline_editor.html.erb +0 -44
- data/app/views/alchemy/ingredients/_html_editor.html.erb +0 -8
- data/app/views/alchemy/ingredients/_link_editor.html.erb +0 -30
- data/app/views/alchemy/ingredients/_node_editor.html.erb +0 -13
- data/app/views/alchemy/ingredients/_number_editor.html.erb +0 -24
- data/app/views/alchemy/ingredients/_page_editor.html.erb +0 -13
- data/app/views/alchemy/ingredients/_picture_editor.html.erb +0 -59
- data/app/views/alchemy/ingredients/_richtext_editor.html.erb +0 -15
- data/app/views/alchemy/ingredients/_select_editor.html.erb +0 -31
- data/app/views/alchemy/ingredients/_text_editor.html.erb +0 -29
- data/app/views/alchemy/ingredients/_video_editor.html.erb +0 -5
- data/lib/generators/alchemy/ingredient/templates/editor.html.erb +0 -14
- /data/{lib → app/models}/alchemy/permissions.rb +0 -0
|
@@ -10,7 +10,7 @@ module Alchemy
|
|
|
10
10
|
@page = page
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
# Copies all currently
|
|
13
|
+
# Copies all currently publishable elements to the public version of page
|
|
14
14
|
#
|
|
15
15
|
# Creates a new published version if none exists yet and updates
|
|
16
16
|
# the `published_at` timestamp of the page.
|
|
@@ -24,10 +24,12 @@ module Alchemy
|
|
|
24
24
|
version = public_version(public_on)
|
|
25
25
|
DeleteElements.new(version.elements).call
|
|
26
26
|
|
|
27
|
+
copy_metadata(public_version: version)
|
|
28
|
+
|
|
27
29
|
repository = page.draft_version.element_repository
|
|
28
30
|
ActiveRecord::Base.no_touching do
|
|
29
31
|
Element.acts_as_list_no_update do
|
|
30
|
-
repository.
|
|
32
|
+
repository.publishable.not_nested.each.with_index(1) do |element, position|
|
|
31
33
|
Alchemy::DuplicateElement.new(element, repository: repository, publishable_only: true).call(
|
|
32
34
|
page_version_id: version.id,
|
|
33
35
|
position: position
|
|
@@ -51,6 +53,16 @@ module Alchemy
|
|
|
51
53
|
def public_version(public_on)
|
|
52
54
|
page.public_version || page.versions.create!(public_on: public_on)
|
|
53
55
|
end
|
|
56
|
+
|
|
57
|
+
# Copy metadata from draft_version to public_version.
|
|
58
|
+
def copy_metadata(public_version:)
|
|
59
|
+
draft = page.draft_version
|
|
60
|
+
return unless draft
|
|
61
|
+
|
|
62
|
+
PageVersion::METADATA_ATTRIBUTES.each do |attr|
|
|
63
|
+
public_version.send(:"#{attr}=", draft.send(attr))
|
|
64
|
+
end
|
|
65
|
+
end
|
|
54
66
|
end
|
|
55
67
|
end
|
|
56
68
|
end
|
data/app/models/alchemy/page.rb
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
# id :integer not null, primary key
|
|
8
8
|
# name :string
|
|
9
9
|
# urlname :string
|
|
10
|
-
# title :string
|
|
10
|
+
# title :string (deprecated - use draft_version.title)
|
|
11
11
|
# language_code :string
|
|
12
12
|
# language_root :boolean
|
|
13
13
|
# page_layout :string
|
|
14
|
-
# meta_keywords :text
|
|
15
|
-
# meta_description :text
|
|
14
|
+
# meta_keywords :text (deprecated - use draft_version.meta_keywords)
|
|
15
|
+
# meta_description :text (deprecated - use draft_version.meta_description)
|
|
16
16
|
# lft :integer
|
|
17
17
|
# rgt :integer
|
|
18
18
|
# parent_id :integer
|
|
@@ -44,9 +44,15 @@ require_dependency "alchemy/page/page_elements"
|
|
|
44
44
|
|
|
45
45
|
module Alchemy
|
|
46
46
|
class Page < BaseRecord
|
|
47
|
-
include Alchemy::Logger
|
|
48
47
|
include Alchemy::Taggable
|
|
49
48
|
|
|
49
|
+
# These columns are deprecated in favor of page versions
|
|
50
|
+
self.ignored_columns += [
|
|
51
|
+
"meta_description",
|
|
52
|
+
"meta_keywords",
|
|
53
|
+
"title"
|
|
54
|
+
]
|
|
55
|
+
|
|
50
56
|
DEFAULT_ATTRIBUTES_FOR_COPY = {
|
|
51
57
|
autogenerate_elements: false,
|
|
52
58
|
public_on: nil,
|
|
@@ -66,11 +72,12 @@ module Alchemy
|
|
|
66
72
|
depth
|
|
67
73
|
urlname
|
|
68
74
|
cached_tag_list
|
|
75
|
+
title
|
|
76
|
+
meta_description
|
|
77
|
+
meta_keywords
|
|
69
78
|
]
|
|
70
79
|
|
|
71
80
|
PERMITTED_ATTRIBUTES = [
|
|
72
|
-
:meta_description,
|
|
73
|
-
:meta_keywords,
|
|
74
81
|
:name,
|
|
75
82
|
:page_layout,
|
|
76
83
|
:public_on,
|
|
@@ -81,33 +88,35 @@ module Alchemy
|
|
|
81
88
|
:searchable,
|
|
82
89
|
:sitemap,
|
|
83
90
|
:tag_list,
|
|
84
|
-
:title,
|
|
85
91
|
:urlname,
|
|
86
92
|
:layoutpage,
|
|
87
|
-
:menu_id
|
|
93
|
+
:menu_id,
|
|
94
|
+
{
|
|
95
|
+
draft_version_attributes: [:id] + PageVersion::METADATA_ATTRIBUTES.map(&:to_sym)
|
|
96
|
+
}
|
|
88
97
|
]
|
|
89
98
|
|
|
90
99
|
acts_as_nested_set(dependent: :destroy, scope: [:layoutpage, :language_id])
|
|
91
100
|
|
|
92
|
-
stampable stamper_class_name: Alchemy.user_class_name
|
|
101
|
+
stampable stamper_class_name: Alchemy.config.user_class_name
|
|
93
102
|
|
|
94
103
|
belongs_to :language
|
|
95
104
|
|
|
96
105
|
belongs_to :creator,
|
|
97
|
-
primary_key: Alchemy.user_class_primary_key,
|
|
98
|
-
class_name: Alchemy.user_class_name,
|
|
106
|
+
primary_key: Alchemy.config.user_class_primary_key,
|
|
107
|
+
class_name: Alchemy.config.user_class_name,
|
|
99
108
|
foreign_key: :creator_id,
|
|
100
109
|
optional: true
|
|
101
110
|
|
|
102
111
|
belongs_to :updater,
|
|
103
|
-
primary_key: Alchemy.user_class_primary_key,
|
|
104
|
-
class_name: Alchemy.user_class_name,
|
|
112
|
+
primary_key: Alchemy.config.user_class_primary_key,
|
|
113
|
+
class_name: Alchemy.config.user_class_name,
|
|
105
114
|
foreign_key: :updater_id,
|
|
106
115
|
optional: true
|
|
107
116
|
|
|
108
117
|
belongs_to :locker,
|
|
109
|
-
primary_key: Alchemy.user_class_primary_key,
|
|
110
|
-
class_name: Alchemy.user_class_name,
|
|
118
|
+
primary_key: Alchemy.config.user_class_primary_key,
|
|
119
|
+
class_name: Alchemy.config.user_class_name,
|
|
111
120
|
foreign_key: :locked_by,
|
|
112
121
|
optional: true
|
|
113
122
|
|
|
@@ -117,8 +126,10 @@ module Alchemy
|
|
|
117
126
|
has_many :legacy_urls, class_name: "Alchemy::LegacyPageUrl", dependent: :destroy
|
|
118
127
|
has_many :nodes, class_name: "Alchemy::Node", inverse_of: :page, dependent: :restrict_with_error
|
|
119
128
|
has_many :versions, class_name: "Alchemy::PageVersion", inverse_of: :page, dependent: :destroy
|
|
120
|
-
has_one :draft_version, -> {
|
|
121
|
-
has_one :public_version, -> { published }, class_name: "Alchemy::PageVersion", autosave: -> { persisted? }
|
|
129
|
+
has_one :draft_version, -> { draft.order(updated_at: :desc) }, class_name: "Alchemy::PageVersion"
|
|
130
|
+
has_one :public_version, -> { published.order(public_on: :desc) }, class_name: "Alchemy::PageVersion", autosave: -> { persisted? }
|
|
131
|
+
|
|
132
|
+
accepts_nested_attributes_for :draft_version
|
|
122
133
|
|
|
123
134
|
has_many :page_ingredients, class_name: "Alchemy::Ingredients::Page", foreign_key: :related_object_id, dependent: :nullify
|
|
124
135
|
|
|
@@ -129,8 +140,7 @@ module Alchemy
|
|
|
129
140
|
validates_format_of :page_layout, with: /\A[a-z0-9_-]+\z/, unless: -> { page_layout.blank? }
|
|
130
141
|
validates_presence_of :parent, unless: -> { layoutpage? || language_root? }
|
|
131
142
|
|
|
132
|
-
|
|
133
|
-
if: -> { versions.none? }
|
|
143
|
+
after_initialize :ensure_draft_version, if: :new_record?
|
|
134
144
|
|
|
135
145
|
before_save :set_language_code,
|
|
136
146
|
if: -> { language.present? }
|
|
@@ -179,7 +189,7 @@ module Alchemy
|
|
|
179
189
|
end
|
|
180
190
|
|
|
181
191
|
def searchable_alchemy_resource_attributes
|
|
182
|
-
%w[name urlname
|
|
192
|
+
%w[name urlname]
|
|
183
193
|
end
|
|
184
194
|
|
|
185
195
|
# @return the language root page for given language id.
|
|
@@ -213,8 +223,7 @@ module Alchemy
|
|
|
213
223
|
.call(changed_attributes: {
|
|
214
224
|
parent: new_parent,
|
|
215
225
|
language: new_parent&.language,
|
|
216
|
-
name: new_name
|
|
217
|
-
title: new_name
|
|
226
|
+
name: new_name
|
|
218
227
|
})
|
|
219
228
|
if source.children.any?
|
|
220
229
|
source.copy_children_to(page)
|
|
@@ -402,6 +411,19 @@ module Alchemy
|
|
|
402
411
|
PublishPageJob.perform_later(id, public_on: current_time)
|
|
403
412
|
end
|
|
404
413
|
|
|
414
|
+
# Returns true if the draft version has changes not yet published.
|
|
415
|
+
#
|
|
416
|
+
# Compares the updated_at timestamp of the draft_version against the
|
|
417
|
+
# updated_at timestamp of the public_version. If there's no public_version,
|
|
418
|
+
# the page has never been published and always has unpublished changes.
|
|
419
|
+
#
|
|
420
|
+
# @return [Boolean]
|
|
421
|
+
def has_unpublished_changes?
|
|
422
|
+
return true unless public_version
|
|
423
|
+
|
|
424
|
+
draft_version.updated_at > public_version.updated_at
|
|
425
|
+
end
|
|
426
|
+
|
|
405
427
|
# Sets the public_on date on the published version
|
|
406
428
|
#
|
|
407
429
|
# Builds a new version if none exists yet.
|
|
@@ -458,6 +480,54 @@ module Alchemy
|
|
|
458
480
|
attribute_fixed?(:public_until) ? fixed_attributes[:public_until] : public_version&.public_until
|
|
459
481
|
end
|
|
460
482
|
|
|
483
|
+
# Returns the title from the public version, falling back to draft version
|
|
484
|
+
#
|
|
485
|
+
# If it's a fixed attribute then the fixed value is returned instead
|
|
486
|
+
#
|
|
487
|
+
def title
|
|
488
|
+
return fixed_attributes[:title] if attribute_fixed?(:title)
|
|
489
|
+
|
|
490
|
+
public_version&.title || draft_version&.title
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# Returns the meta_description from the public version, falling back to draft version
|
|
494
|
+
#
|
|
495
|
+
# If it's a fixed attribute then the fixed value is returned instead
|
|
496
|
+
#
|
|
497
|
+
def meta_description
|
|
498
|
+
return fixed_attributes[:meta_description] if attribute_fixed?(:meta_description)
|
|
499
|
+
|
|
500
|
+
public_version&.meta_description || draft_version&.meta_description
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# Returns the meta_keywords from the public version, falling back to draft version
|
|
504
|
+
#
|
|
505
|
+
# If it's a fixed attribute then the fixed value is returned instead
|
|
506
|
+
#
|
|
507
|
+
def meta_keywords
|
|
508
|
+
return fixed_attributes[:meta_keywords] if attribute_fixed?(:meta_keywords)
|
|
509
|
+
|
|
510
|
+
public_version&.meta_keywords || draft_version&.meta_keywords
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# @deprecated Use draft_version.title= instead
|
|
514
|
+
def title=(value)
|
|
515
|
+
draft_version&.title = value
|
|
516
|
+
end
|
|
517
|
+
deprecate "title=": :"page.draft_version.title=", deprecator: Alchemy::Deprecation
|
|
518
|
+
|
|
519
|
+
# @deprecated Use draft_version.meta_description= instead
|
|
520
|
+
def meta_description=(value)
|
|
521
|
+
draft_version&.meta_description = value
|
|
522
|
+
end
|
|
523
|
+
deprecate "meta_description=": :"page.draft_version.meta_description=", deprecator: Alchemy::Deprecation
|
|
524
|
+
|
|
525
|
+
# @deprecated Use draft_version.meta_keywords= instead
|
|
526
|
+
def meta_keywords=(value)
|
|
527
|
+
draft_version&.meta_keywords = value
|
|
528
|
+
end
|
|
529
|
+
deprecate "meta_keywords=": :"page.draft_version.meta_keywords=", deprecator: Alchemy::Deprecation
|
|
530
|
+
|
|
461
531
|
# Returns the name of the creator of this page.
|
|
462
532
|
#
|
|
463
533
|
# If no creator could be found or associated user model
|
|
@@ -499,9 +569,18 @@ module Alchemy
|
|
|
499
569
|
|
|
500
570
|
private
|
|
501
571
|
|
|
572
|
+
def ensure_draft_version
|
|
573
|
+
self.draft_version ||= versions.build
|
|
574
|
+
end
|
|
575
|
+
|
|
502
576
|
def set_fixed_attributes
|
|
503
577
|
fixed_attributes.all.each do |attribute, value|
|
|
504
|
-
|
|
578
|
+
attribute_name = attribute.to_s
|
|
579
|
+
if PageVersion::METADATA_ATTRIBUTES.include?(attribute_name)
|
|
580
|
+
draft_version&.send(:"#{attribute}=", value)
|
|
581
|
+
else
|
|
582
|
+
send(:"#{attribute}=", value)
|
|
583
|
+
end
|
|
505
584
|
end
|
|
506
585
|
end
|
|
507
586
|
|
|
@@ -4,7 +4,6 @@ module Alchemy
|
|
|
4
4
|
class PageDefinition
|
|
5
5
|
include ActiveModel::Model
|
|
6
6
|
include ActiveModel::Attributes
|
|
7
|
-
include Alchemy::Hints
|
|
8
7
|
|
|
9
8
|
extend ActiveModel::Translation
|
|
10
9
|
|
|
@@ -22,6 +21,10 @@ module Alchemy
|
|
|
22
21
|
attribute :editable_by
|
|
23
22
|
attribute :hint
|
|
24
23
|
|
|
24
|
+
# Needs to be down here in order to have the attribute reader
|
|
25
|
+
# available after the attribute is defined.
|
|
26
|
+
include Alchemy::Hints
|
|
27
|
+
|
|
25
28
|
validates :name,
|
|
26
29
|
presence: true,
|
|
27
30
|
format: {
|
|
@@ -2,19 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
module Alchemy
|
|
4
4
|
class PageVersion < BaseRecord
|
|
5
|
+
include Alchemy::Publishable
|
|
6
|
+
|
|
7
|
+
# Metadata attributes that are versioned (moved from Page)
|
|
8
|
+
METADATA_ATTRIBUTES = %w[
|
|
9
|
+
title
|
|
10
|
+
meta_description
|
|
11
|
+
meta_keywords
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
5
14
|
belongs_to :page, class_name: "Alchemy::Page", inverse_of: :versions, touch: true
|
|
6
15
|
|
|
7
16
|
has_many :elements, -> { order(:position) },
|
|
8
17
|
class_name: "Alchemy::Element",
|
|
9
18
|
inverse_of: :page_version
|
|
10
19
|
|
|
11
|
-
|
|
12
|
-
|
|
20
|
+
before_create :set_title_from_page
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
alias_method :drafts, :draft
|
|
24
|
+
deprecate drafts: :draft, deprecator: Alchemy::Deprecation
|
|
13
25
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"(#{table_name}.public_until IS NULL " \
|
|
17
|
-
"OR #{table_name}.public_until >= :time)", time: time)
|
|
26
|
+
alias_method :public_on, :published
|
|
27
|
+
deprecate public_on: :published, deprecator: Alchemy::Deprecation
|
|
18
28
|
end
|
|
19
29
|
|
|
20
30
|
before_destroy :delete_elements
|
|
@@ -54,5 +64,11 @@ module Alchemy
|
|
|
54
64
|
def delete_elements
|
|
55
65
|
DeleteElements.new(elements).call
|
|
56
66
|
end
|
|
67
|
+
|
|
68
|
+
def set_title_from_page
|
|
69
|
+
return if title.present?
|
|
70
|
+
|
|
71
|
+
self.title = page&.name
|
|
72
|
+
end
|
|
57
73
|
end
|
|
58
74
|
end
|
|
@@ -28,7 +28,6 @@ module Alchemy
|
|
|
28
28
|
large: "240x180"
|
|
29
29
|
}.with_indifferent_access.freeze
|
|
30
30
|
|
|
31
|
-
include Alchemy::Logger
|
|
32
31
|
include Alchemy::NameConversions
|
|
33
32
|
include Alchemy::Taggable
|
|
34
33
|
include Alchemy::TouchElements
|
|
@@ -63,6 +62,8 @@ module Alchemy
|
|
|
63
62
|
@_preprocessor_class = klass
|
|
64
63
|
end
|
|
65
64
|
|
|
65
|
+
before_create :set_name, if: :image_file_name
|
|
66
|
+
|
|
66
67
|
include Alchemy.storage_adapter.picture_class_methods
|
|
67
68
|
|
|
68
69
|
# We need to define this method here to have it available in the validations below.
|
|
@@ -76,7 +77,7 @@ module Alchemy
|
|
|
76
77
|
validates_size_of :image_file, maximum: Alchemy.config.uploader.file_size_limit.megabytes
|
|
77
78
|
validate :image_file_type_allowed, if: -> { image_file.present? }
|
|
78
79
|
|
|
79
|
-
stampable stamper_class_name: Alchemy.user_class_name
|
|
80
|
+
stampable stamper_class_name: Alchemy.config.user_class_name
|
|
80
81
|
|
|
81
82
|
scope :named, ->(name) { where("#{table_name}.name LIKE ?", "%#{name}%") }
|
|
82
83
|
scope :recent, -> { where("#{table_name}.created_at > ?", Time.current - 24.hours).order(:created_at) }
|
|
@@ -148,7 +149,7 @@ module Alchemy
|
|
|
148
149
|
|
|
149
150
|
self.class.url_class.new(self).call(options)
|
|
150
151
|
rescue Alchemy.storage_adapter.rescuable_errors => e
|
|
151
|
-
|
|
152
|
+
Logger.warn(e.message)
|
|
152
153
|
nil
|
|
153
154
|
end
|
|
154
155
|
|
|
@@ -196,14 +197,6 @@ module Alchemy
|
|
|
196
197
|
end
|
|
197
198
|
end
|
|
198
199
|
|
|
199
|
-
# Returns a humanized, readable name from image filename.
|
|
200
|
-
#
|
|
201
|
-
def humanized_name
|
|
202
|
-
return "" if image_file_name.blank?
|
|
203
|
-
|
|
204
|
-
convert_to_humanized_name(image_file_name, image_file_extension)
|
|
205
|
-
end
|
|
206
|
-
|
|
207
200
|
# Returns the format the image should be rendered with
|
|
208
201
|
#
|
|
209
202
|
# Only returns a format differing from original if an +image_output_format+
|
|
@@ -288,6 +281,12 @@ module Alchemy
|
|
|
288
281
|
|
|
289
282
|
private
|
|
290
283
|
|
|
284
|
+
# Returns a humanized, readable name from image filename.
|
|
285
|
+
#
|
|
286
|
+
def set_name
|
|
287
|
+
self.name ||= Alchemy.storage_adapter.image_file_basename(self).humanize
|
|
288
|
+
end
|
|
289
|
+
|
|
291
290
|
def image_file_type_allowed
|
|
292
291
|
unless image_file_extension&.in?(self.class.allowed_filetypes)
|
|
293
292
|
errors.add(:image_file, Alchemy.t("not a valid image"))
|
|
@@ -10,8 +10,6 @@ module Alchemy
|
|
|
10
10
|
class PictureVariant
|
|
11
11
|
extend Forwardable
|
|
12
12
|
|
|
13
|
-
include Alchemy::Logger
|
|
14
|
-
|
|
15
13
|
ANIMATED_IMAGE_FORMATS = %w[gif webp]
|
|
16
14
|
TRANSPARENT_IMAGE_FORMATS = %w[gif webp png]
|
|
17
15
|
ENCODABLE_IMAGE_FORMATS = %w[jpg jpeg webp]
|
|
@@ -57,7 +55,7 @@ module Alchemy
|
|
|
57
55
|
image = processed_image(image, @options)
|
|
58
56
|
encoded_image(image, @options)
|
|
59
57
|
rescue MissingImageFileError, WrongImageFormatError => e
|
|
60
|
-
|
|
58
|
+
Logger.warn(e.message)
|
|
61
59
|
nil
|
|
62
60
|
end
|
|
63
61
|
|
|
@@ -4,8 +4,6 @@ module Alchemy
|
|
|
4
4
|
module PictureClassMethods
|
|
5
5
|
def self.included(base)
|
|
6
6
|
base.has_one_attached :image_file do |attachable|
|
|
7
|
-
# Only works in Rails 7.1+
|
|
8
|
-
# https://github.com/rails/rails/pull/47473
|
|
9
7
|
Alchemy.storage_adapter.preprocessor_class.new(attachable).call
|
|
10
8
|
Alchemy.storage_adapter.preprocessor_class.generate_thumbs!(attachable)
|
|
11
9
|
end
|
|
@@ -98,6 +96,13 @@ module Alchemy
|
|
|
98
96
|
Attachment.with_attached_file.joins(:file_blob).where.not(active_storage_blobs: {content_type: file_type})
|
|
99
97
|
end
|
|
100
98
|
|
|
99
|
+
# @param [Alchemy::Attachment]
|
|
100
|
+
# @return [String]
|
|
101
|
+
def file_basename(attachment)
|
|
102
|
+
filename = attachment.file&.filename.to_s
|
|
103
|
+
File.basename(filename, File.extname(filename))
|
|
104
|
+
end
|
|
105
|
+
|
|
101
106
|
# @param [Alchemy::Attachment]
|
|
102
107
|
# @return [String]
|
|
103
108
|
def file_name(attachment)
|
|
@@ -128,6 +133,13 @@ module Alchemy
|
|
|
128
133
|
picture.image_file&.variable?
|
|
129
134
|
end
|
|
130
135
|
|
|
136
|
+
# @param [Alchemy::Picture]
|
|
137
|
+
# @return [String]
|
|
138
|
+
def image_file_basename(picture)
|
|
139
|
+
filename = picture.image_file&.filename.to_s
|
|
140
|
+
File.basename(filename, File.extname(filename))
|
|
141
|
+
end
|
|
142
|
+
|
|
131
143
|
# @param [Alchemy::Picture]
|
|
132
144
|
# @return [String]
|
|
133
145
|
def image_file_name(picture)
|
|
@@ -127,6 +127,12 @@ module Alchemy
|
|
|
127
127
|
attachment.read_attribute(:file_name)
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
+
# @param [Alchemy::Attachment]
|
|
131
|
+
# @return [String]
|
|
132
|
+
def file_basename(attachment)
|
|
133
|
+
attachment.file&.basename&.to_s
|
|
134
|
+
end
|
|
135
|
+
|
|
130
136
|
# @param [Alchemy::Attachment]
|
|
131
137
|
# @return [Integer]
|
|
132
138
|
def file_size(attachment)
|
|
@@ -152,6 +158,12 @@ module Alchemy
|
|
|
152
158
|
image_file_extension(picture).in?(CONVERTIBLE_FILE_FORMATS)
|
|
153
159
|
end
|
|
154
160
|
|
|
161
|
+
# @param [Alchemy::Picture]
|
|
162
|
+
# @return [String]
|
|
163
|
+
def image_file_basename(picture)
|
|
164
|
+
picture.image_file&.basename&.to_s
|
|
165
|
+
end
|
|
166
|
+
|
|
155
167
|
# @param [Alchemy::Picture]
|
|
156
168
|
# @return [String]
|
|
157
169
|
def image_file_name(picture)
|
|
@@ -9,12 +9,14 @@ module Alchemy
|
|
|
9
9
|
:by_file_format_scope,
|
|
10
10
|
:by_file_type_scope,
|
|
11
11
|
:not_file_type_scope,
|
|
12
|
+
:file_basename,
|
|
12
13
|
:file_extension,
|
|
13
14
|
:file_formats,
|
|
14
15
|
:file_mime_type,
|
|
15
16
|
:file_name,
|
|
16
17
|
:file_size,
|
|
17
18
|
:has_convertible_format?,
|
|
19
|
+
:image_file_basename,
|
|
18
20
|
:image_file_extension,
|
|
19
21
|
:image_file_format,
|
|
20
22
|
:image_file_height,
|
|
@@ -66,20 +66,20 @@ module Alchemy
|
|
|
66
66
|
# image displayed in the frontend.
|
|
67
67
|
#
|
|
68
68
|
# @return [String]
|
|
69
|
-
def thumbnail_url
|
|
69
|
+
def thumbnail_url(size: "160x120")
|
|
70
70
|
return if picture.nil?
|
|
71
71
|
|
|
72
|
-
picture.url(thumbnail_url_options) || "alchemy/missing-image.svg"
|
|
72
|
+
picture.url(thumbnail_url_options(size: size)) || "alchemy/missing-image.svg"
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
# Thumbnail rendering options
|
|
76
76
|
#
|
|
77
77
|
# @return [HashWithIndifferentAccess]
|
|
78
|
-
def thumbnail_url_options
|
|
78
|
+
def thumbnail_url_options(size: "160x120")
|
|
79
79
|
crop = !!settings[:crop]
|
|
80
80
|
|
|
81
81
|
{
|
|
82
|
-
size:
|
|
82
|
+
size: size,
|
|
83
83
|
crop: crop,
|
|
84
84
|
crop_from: crop && crop_from.presence || default_crop_from&.join("x"),
|
|
85
85
|
crop_size: crop && crop_size.presence || default_crop_size&.join("x"),
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Alchemy
|
|
2
|
+
module Publishable
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
scope :draft, -> { where(public_on: nil) }
|
|
7
|
+
scope :scheduled, -> { where.not(public_on: nil) }
|
|
8
|
+
|
|
9
|
+
scope :published, ->(at: Time.current) {
|
|
10
|
+
scheduled
|
|
11
|
+
.where("#{table_name}.public_on <= :at", at:)
|
|
12
|
+
.where(public_until: nil).or(
|
|
13
|
+
where("#{table_name}.public_until > :at", at:)
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Determines if this record is public
|
|
19
|
+
#
|
|
20
|
+
# Takes the two timestamps +public_on+ and +public_until+
|
|
21
|
+
# and returns true if the time given (+Time.current+ per default)
|
|
22
|
+
# is in this timespan.
|
|
23
|
+
#
|
|
24
|
+
# @param time [DateTime] (Time.current)
|
|
25
|
+
# @returns Boolean
|
|
26
|
+
def public?(time = Time.current)
|
|
27
|
+
already_public_for?(time) && still_public_for?(time)
|
|
28
|
+
end
|
|
29
|
+
alias_method :public, :public?
|
|
30
|
+
|
|
31
|
+
# Determines if this record is publishable
|
|
32
|
+
#
|
|
33
|
+
# A record is publishable if a +public_on+ timestamp is set and not expired yet.
|
|
34
|
+
#
|
|
35
|
+
# @returns Boolean
|
|
36
|
+
def publishable?
|
|
37
|
+
!public_on.nil? && still_public_for?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Determines if this record is already public for given time
|
|
41
|
+
# @param time [DateTime] (Time.current)
|
|
42
|
+
# @returns Boolean
|
|
43
|
+
def already_public_for?(time = Time.current)
|
|
44
|
+
!public_on.nil? && public_on <= time
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Determines if this record is still public for given time
|
|
48
|
+
# @param time [DateTime] (Time.current)
|
|
49
|
+
# @returns Boolean
|
|
50
|
+
def still_public_for?(time = Time.current)
|
|
51
|
+
public_until.nil? || public_until >= time
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -7,43 +7,23 @@ module Alchemy
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def pages
|
|
10
|
-
tree = []
|
|
11
|
-
path = [{id: object.parent_id, children: tree}]
|
|
12
|
-
page_list = object.self_and_descendants.includes(:public_version, {language: :site})
|
|
13
10
|
base_level = object.level - 1
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
folded_depth = Float::INFINITY
|
|
17
|
-
|
|
18
|
-
page_list.each_with_index do |page, i|
|
|
19
|
-
has_children = page_list[i + 1] && page_list[i + 1].parent_id == page.id
|
|
20
|
-
folded = has_children && folded_user_pages.include?(page.id)
|
|
11
|
+
build_pages_tree([object], base_level)
|
|
12
|
+
end
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
next
|
|
24
|
-
else
|
|
25
|
-
folded_depth = Float::INFINITY
|
|
26
|
-
end
|
|
14
|
+
private
|
|
27
15
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
16
|
+
def build_pages_tree(pages, level)
|
|
17
|
+
pages.map do |page|
|
|
18
|
+
# Use association target directly to avoid triggering queries
|
|
19
|
+
children = page.association(:children).loaded? ? page.association(:children).target : []
|
|
20
|
+
has_children = children.any?
|
|
21
|
+
folded = !has_children && !page.leaf?
|
|
32
22
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
path.pop while path.last[:id] != page.parent_id
|
|
36
|
-
else # One level up
|
|
37
|
-
path << path.last[:children].last
|
|
38
|
-
end
|
|
23
|
+
page_hash(page, level, folded).tap do |hash|
|
|
24
|
+
hash[:children] = build_pages_tree(children, level + 1)
|
|
39
25
|
end
|
|
40
|
-
|
|
41
|
-
level = path.count + base_level
|
|
42
|
-
|
|
43
|
-
path.last[:children] << page_hash(page, level, folded)
|
|
44
26
|
end
|
|
45
|
-
|
|
46
|
-
tree
|
|
47
27
|
end
|
|
48
28
|
|
|
49
29
|
protected
|
|
@@ -28,8 +28,14 @@ module Alchemy
|
|
|
28
28
|
depth
|
|
29
29
|
urlname
|
|
30
30
|
cached_tag_list
|
|
31
|
+
title
|
|
32
|
+
meta_description
|
|
33
|
+
meta_keywords
|
|
31
34
|
]
|
|
32
35
|
|
|
36
|
+
# Metadata to copy via nested attributes (title is derived from page.name)
|
|
37
|
+
METADATA_ATTRIBUTES_TO_COPY = (Alchemy::PageVersion::METADATA_ATTRIBUTES - %w[title]).freeze
|
|
38
|
+
|
|
33
39
|
attr_reader :page
|
|
34
40
|
|
|
35
41
|
# @param page [Alchemy::Page]
|
|
@@ -70,6 +76,7 @@ module Alchemy
|
|
|
70
76
|
.merge(DEFAULT_ATTRIBUTES_FOR_COPY)
|
|
71
77
|
.merge(differences)
|
|
72
78
|
desired_attributes["name"] = best_name_for_copy(source_attributes, desired_attributes)
|
|
79
|
+
desired_attributes["draft_version_attributes"] = draft_version_attributes_for_copy
|
|
73
80
|
desired_attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY)
|
|
74
81
|
end
|
|
75
82
|
|
|
@@ -94,5 +101,15 @@ module Alchemy
|
|
|
94
101
|
desired_name
|
|
95
102
|
end
|
|
96
103
|
end
|
|
104
|
+
|
|
105
|
+
# Builds nested attributes for draft_version metadata (except title).
|
|
106
|
+
# Title is handled by PageVersion#set_title_from_page callback based on page.name.
|
|
107
|
+
def draft_version_attributes_for_copy
|
|
108
|
+
return {} unless page.draft_version
|
|
109
|
+
|
|
110
|
+
METADATA_ATTRIBUTES_TO_COPY.each_with_object({}) do |attr, hash|
|
|
111
|
+
hash[attr] = page.draft_version.send(attr)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
97
114
|
end
|
|
98
115
|
end
|
|
@@ -36,7 +36,7 @@ module Alchemy
|
|
|
36
36
|
new_element.save!
|
|
37
37
|
|
|
38
38
|
nested_elements = repository.children_of(source_element)
|
|
39
|
-
nested_elements = nested_elements.
|
|
39
|
+
nested_elements = nested_elements.publishable if publishable_only
|
|
40
40
|
Element.acts_as_list_no_update do
|
|
41
41
|
nested_elements.each.with_index(1) do |nested_element, position|
|
|
42
42
|
self.class.new(nested_element, repository: repository, publishable_only: publishable_only).call(
|