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
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{var n=Handlebars.template
|
|
1
|
+
(()=>{var n=Handlebars.template;(Handlebars.templates=Handlebars.templates||{})["node_folder.hbs"]=n({1:function(n,e,l,a,r){return"right"},3:function(n,e,l,a,r){return"down"},compiler:[8,">= 4.3.0"],main:function(n,e,l,a,r){var o,t=n.lambda,d=n.escapeExpression,u=n.lookupProperty||function(n,e){if(Object.prototype.hasOwnProperty.call(n,e))return n[e]};return'<a class="node_folder" data-record-id="'+d(t(null!=(o=null!=e?u(e,"node"):e)?u(o,"id"):o,e))+'" data-record-type="'+d(t(null!=(o=null!=e?u(e,"node"):e)?u(o,"type"):o,e))+'">\n <alchemy-icon name="arrow-'+(null!=(o=u(l,"if").call(null!=e?e:n.nullContext||{},null!=(o=null!=e?u(e,"node"):e)?u(o,"folded"):o,{name:"if",hash:{},fn:n.program(1,r,0),inverse:n.program(3,r,0),data:r,loc:{start:{line:2,column:28},end:{line:2,column:72}}}))?o:"")+'-s"></alchemy-icon>\n</a>\n'},useData:!0})})();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const JSON_CONTENT_TYPE = "application/json"
|
|
2
|
+
const TURBO_STREAM_CONTENT_TYPE = "text/vnd.turbo-stream.html"
|
|
2
3
|
|
|
3
4
|
function isGetRequest(method) {
|
|
4
5
|
return method.toLowerCase() === "get"
|
|
@@ -43,8 +44,8 @@ export function get(url, params) {
|
|
|
43
44
|
return ajax("GET", url, params)
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
export function patch(url, data) {
|
|
47
|
-
return ajax("PATCH", url, data)
|
|
47
|
+
export function patch(url, data, accept) {
|
|
48
|
+
return ajax("PATCH", url, data, accept)
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
export function post(url, data, accept = JSON_CONTENT_TYPE) {
|
|
@@ -63,7 +64,18 @@ export default async function ajax(
|
|
|
63
64
|
)
|
|
64
65
|
const contentType = response.headers.get("content-type")
|
|
65
66
|
const isJson = contentType?.includes(JSON_CONTENT_TYPE)
|
|
66
|
-
const
|
|
67
|
+
const isTurboStream = contentType?.includes(TURBO_STREAM_CONTENT_TYPE)
|
|
68
|
+
|
|
69
|
+
let responseData = null
|
|
70
|
+
if (isJson) {
|
|
71
|
+
responseData = await response.json()
|
|
72
|
+
} else if (isTurboStream) {
|
|
73
|
+
responseData = await response.text()
|
|
74
|
+
// Automatically render Turbo Stream if Turbo is available
|
|
75
|
+
if (typeof Turbo !== "undefined") {
|
|
76
|
+
Turbo.renderStreamMessage(responseData)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
67
79
|
|
|
68
80
|
if (response.ok) {
|
|
69
81
|
return { data: responseData, status: response.status }
|
|
@@ -12,13 +12,10 @@ import { currentDialog, closeCurrentDialog } from "alchemy_admin/dialog"
|
|
|
12
12
|
import Dirty from "alchemy_admin/dirty"
|
|
13
13
|
import * as FixedElements from "alchemy_admin/fixed_elements"
|
|
14
14
|
import { growl } from "alchemy_admin/growler"
|
|
15
|
-
import ImageLoader from "alchemy_admin/image_loader"
|
|
16
15
|
import Initializer from "alchemy_admin/initializer"
|
|
17
16
|
import { LinkDialog } from "alchemy_admin/link_dialog"
|
|
18
17
|
import pleaseWaitOverlay from "alchemy_admin/please_wait_overlay"
|
|
19
|
-
import Sitemap from "alchemy_admin/sitemap"
|
|
20
18
|
import Spinner from "alchemy_admin/spinner"
|
|
21
|
-
import PagePublicationFields from "alchemy_admin/page_publication_fields"
|
|
22
19
|
import { reloadPreview } from "alchemy_admin/components/preview_window"
|
|
23
20
|
import { openConfirmDialog } from "alchemy_admin/confirm_dialog"
|
|
24
21
|
|
|
@@ -44,12 +41,9 @@ Object.assign(Alchemy, {
|
|
|
44
41
|
t: translate, // Global utility method for translating a given string
|
|
45
42
|
FixedElements,
|
|
46
43
|
growl,
|
|
47
|
-
ImageLoader: ImageLoader.init,
|
|
48
44
|
LinkDialog,
|
|
49
45
|
pleaseWaitOverlay,
|
|
50
|
-
Sitemap,
|
|
51
46
|
Spinner,
|
|
52
|
-
PagePublicationFields,
|
|
53
47
|
reloadPreview
|
|
54
48
|
})
|
|
55
49
|
|
|
@@ -25,9 +25,11 @@ module Alchemy
|
|
|
25
25
|
include Alchemy::TouchElements
|
|
26
26
|
include Alchemy::RelatableResource
|
|
27
27
|
|
|
28
|
+
before_save :set_name, if: -> { Alchemy.storage_adapter.set_attachment_name?(self) }
|
|
29
|
+
|
|
28
30
|
include Alchemy.storage_adapter.attachment_class_methods
|
|
29
31
|
|
|
30
|
-
stampable stamper_class_name: Alchemy.user_class_name
|
|
32
|
+
stampable stamper_class_name: Alchemy.config.user_class_name
|
|
31
33
|
|
|
32
34
|
scope :by_file_type, ->(*file_type) do
|
|
33
35
|
Alchemy.storage_adapter.by_file_type_scope(file_type)
|
|
@@ -97,8 +99,6 @@ module Alchemy
|
|
|
97
99
|
validate :file_type_allowed,
|
|
98
100
|
unless: -> { self.class.allowed_filetypes.include?("*") }
|
|
99
101
|
|
|
100
|
-
before_save :set_name, if: -> { Alchemy.storage_adapter.set_attachment_name?(self) }
|
|
101
|
-
|
|
102
102
|
scope :with_file_type, ->(file_type) { where(file_mime_type: file_type) }
|
|
103
103
|
|
|
104
104
|
# Instance methods
|
|
@@ -179,7 +179,7 @@ module Alchemy
|
|
|
179
179
|
end
|
|
180
180
|
|
|
181
181
|
def set_name
|
|
182
|
-
self.name ||=
|
|
182
|
+
self.name ||= Alchemy.storage_adapter.file_basename(self).humanize
|
|
183
183
|
end
|
|
184
184
|
end
|
|
185
185
|
end
|
|
@@ -44,8 +44,7 @@ module Alchemy
|
|
|
44
44
|
if (definition = self.class.definition_by_name(name))
|
|
45
45
|
definition
|
|
46
46
|
else
|
|
47
|
-
|
|
48
|
-
"Please check your elements.yml file!"
|
|
47
|
+
Logger.warn "Could not find element definition for '#{name}'! Please check your element definitions."
|
|
49
48
|
ElementDefinition.new
|
|
50
49
|
end
|
|
51
50
|
end
|
|
@@ -61,8 +61,12 @@ module Alchemy
|
|
|
61
61
|
if ingredient_definitions.blank?
|
|
62
62
|
nil
|
|
63
63
|
else
|
|
64
|
-
ingredient_definitions.find { _1.role == role.to_s }
|
|
65
|
-
|
|
64
|
+
definition = ingredient_definitions.find { _1.role == role.to_s }
|
|
65
|
+
return definition if definition.present?
|
|
66
|
+
|
|
67
|
+
Logger.warn <<-WARN.strip_heredoc
|
|
68
|
+
Element '#{name}' is missing the ingredient definition for '#{role}'! Please check your element definitions.
|
|
69
|
+
WARN
|
|
66
70
|
end
|
|
67
71
|
end
|
|
68
72
|
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
# name :string
|
|
9
9
|
# position :integer
|
|
10
10
|
# page_version_id :integer not null
|
|
11
|
-
#
|
|
11
|
+
# public_on :timestamp
|
|
12
|
+
# public_until :timestamp
|
|
12
13
|
# fixed :boolean default(FALSE)
|
|
13
14
|
# folded :boolean default(FALSE)
|
|
14
15
|
# unique :boolean default(FALSE)
|
|
@@ -28,8 +29,13 @@ module Alchemy
|
|
|
28
29
|
class Element < BaseRecord
|
|
29
30
|
NAME_REGEXP = /\A[a-z0-9_-]+\z/
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
# These columns are deprecated in favor of publication time stamps
|
|
33
|
+
self.ignored_columns += [
|
|
34
|
+
"public"
|
|
35
|
+
]
|
|
36
|
+
|
|
32
37
|
include Alchemy::Taggable
|
|
38
|
+
include Publishable
|
|
33
39
|
|
|
34
40
|
FORBIDDEN_DEFINITION_ATTRIBUTES = [
|
|
35
41
|
"amount",
|
|
@@ -56,7 +62,7 @@ module Alchemy
|
|
|
56
62
|
#
|
|
57
63
|
acts_as_list scope: [:page_version_id, :fixed, :parent_element_id]
|
|
58
64
|
|
|
59
|
-
stampable stamper_class_name: Alchemy.user_class_name
|
|
65
|
+
stampable stamper_class_name: Alchemy.config.user_class_name
|
|
60
66
|
|
|
61
67
|
before_destroy :delete_all_nested_elements
|
|
62
68
|
|
|
@@ -91,13 +97,13 @@ module Alchemy
|
|
|
91
97
|
validates_presence_of :name, on: :create
|
|
92
98
|
validates_format_of :name, on: :create, with: NAME_REGEXP
|
|
93
99
|
|
|
100
|
+
after_initialize :set_default_public_on, if: :new_record?
|
|
101
|
+
|
|
94
102
|
attr_accessor :autogenerate_nested_elements
|
|
95
103
|
after_create :generate_nested_elements, unless: -> { autogenerate_nested_elements == false }
|
|
96
104
|
|
|
97
105
|
after_update :touch_touchable_pages
|
|
98
106
|
|
|
99
|
-
scope :published, -> { where(public: true) }
|
|
100
|
-
scope :hidden, -> { where(public: false) }
|
|
101
107
|
scope :not_restricted, -> { joins(:page).merge(Page.not_restricted) }
|
|
102
108
|
scope :named, ->(names) { where(name: names) }
|
|
103
109
|
scope :excluded, ->(names) { where.not(name: names) }
|
|
@@ -118,6 +124,9 @@ module Alchemy
|
|
|
118
124
|
|
|
119
125
|
# class methods
|
|
120
126
|
class << self
|
|
127
|
+
alias_method :hidden, :draft
|
|
128
|
+
deprecate hidden: :draft, deprecator: Alchemy::Deprecation
|
|
129
|
+
|
|
121
130
|
# Builds a new element as described in +/config/alchemy/elements.yml+
|
|
122
131
|
#
|
|
123
132
|
# - Returns a new Alchemy::Element object if no name is given in attributes,
|
|
@@ -174,13 +183,21 @@ module Alchemy
|
|
|
174
183
|
end
|
|
175
184
|
end
|
|
176
185
|
|
|
177
|
-
#
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
# Returns IDs of all folded parent elements from immediate parent up to root
|
|
187
|
+
#
|
|
188
|
+
# Walks up the ancestor chain and collects only the ones that are folded,
|
|
189
|
+
# skipping already expanded parents.
|
|
190
|
+
#
|
|
191
|
+
# @return [Array<Integer>] Folded parent element IDs from immediate parent to root
|
|
192
|
+
def folded_parent_element_ids
|
|
193
|
+
return [] unless parent_element_id
|
|
194
|
+
|
|
195
|
+
ids = []
|
|
196
|
+
current_id = parent_element_id
|
|
197
|
+
while current_id
|
|
198
|
+
folded, parent_id = self.class.where(id: current_id).pick(:folded, :parent_element_id)
|
|
199
|
+
ids << current_id if folded
|
|
200
|
+
current_id = parent_id
|
|
184
201
|
end
|
|
185
202
|
ids
|
|
186
203
|
end
|
|
@@ -212,6 +229,25 @@ module Alchemy
|
|
|
212
229
|
end
|
|
213
230
|
end
|
|
214
231
|
|
|
232
|
+
# Convenience setter to set public_on attribute
|
|
233
|
+
# when setting public to true or false.
|
|
234
|
+
def public=(value)
|
|
235
|
+
@public_on_explicitely_set = true
|
|
236
|
+
if ActiveModel::Type::Boolean.new.cast(value)
|
|
237
|
+
self.public_on ||= Time.current
|
|
238
|
+
else
|
|
239
|
+
self.public_on = nil
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Override setter to track if public_on was already set
|
|
244
|
+
# in order to not override it with default value if someone
|
|
245
|
+
# explicitly set it to nil.
|
|
246
|
+
def public_on=(value)
|
|
247
|
+
@public_on_explicitely_set = true
|
|
248
|
+
super
|
|
249
|
+
end
|
|
250
|
+
|
|
215
251
|
# Returns true if the definition of this element has a taggable true value.
|
|
216
252
|
def taggable?
|
|
217
253
|
definition.taggable == true
|
|
@@ -284,12 +320,17 @@ module Alchemy
|
|
|
284
320
|
|
|
285
321
|
private
|
|
286
322
|
|
|
323
|
+
def set_default_public_on
|
|
324
|
+
return if @public_on_explicitely_set
|
|
325
|
+
self.public_on ||= Time.current
|
|
326
|
+
end
|
|
327
|
+
|
|
287
328
|
def generate_nested_elements
|
|
288
329
|
definition.autogenerate.each do |nestable_element|
|
|
289
330
|
if nestable_elements.include?(nestable_element)
|
|
290
331
|
Element.create(page_version: page_version, parent_element_id: id, name: nestable_element)
|
|
291
332
|
else
|
|
292
|
-
|
|
333
|
+
Logger.warn("Element '#{nestable_element}' not a nestable element for '#{name}'. Skipping!")
|
|
293
334
|
end
|
|
294
335
|
end
|
|
295
336
|
end
|
|
@@ -4,7 +4,6 @@ module Alchemy
|
|
|
4
4
|
class ElementDefinition
|
|
5
5
|
include ActiveModel::Model
|
|
6
6
|
include ActiveModel::Attributes
|
|
7
|
-
include Alchemy::Hints
|
|
8
7
|
|
|
9
8
|
extend ActiveModel::Translation
|
|
10
9
|
|
|
@@ -24,6 +23,10 @@ module Alchemy
|
|
|
24
23
|
attribute :icon
|
|
25
24
|
attribute :searchable, :boolean, default: true
|
|
26
25
|
|
|
26
|
+
# Needs to be down here in order to have the attribute reader
|
|
27
|
+
# available after the attribute is defined.
|
|
28
|
+
include Alchemy::Hints
|
|
29
|
+
|
|
27
30
|
validates :name,
|
|
28
31
|
presence: true,
|
|
29
32
|
format: {
|
|
@@ -22,6 +22,12 @@ module Alchemy
|
|
|
22
22
|
self.class.new select(&:public)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# All publishable elements
|
|
26
|
+
# @return [Alchemy::ElementRepository]
|
|
27
|
+
def publishable
|
|
28
|
+
self.class.new select(&:publishable?)
|
|
29
|
+
end
|
|
30
|
+
|
|
25
31
|
# All not fixed elements
|
|
26
32
|
# @return [Alchemy::ElementRepository]
|
|
27
33
|
def hidden
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
module Alchemy
|
|
14
14
|
class FoldedPage < BaseRecord
|
|
15
15
|
belongs_to :page, inverse_of: :folded_pages
|
|
16
|
-
belongs_to :user, inverse_of: :folded_pages, class_name: Alchemy.user_class_name
|
|
16
|
+
belongs_to :user, inverse_of: :folded_pages, class_name: Alchemy.config.user_class_name
|
|
17
17
|
|
|
18
18
|
def self.folded_for_user(user)
|
|
19
|
-
return none unless Alchemy.user_class
|
|
19
|
+
return none unless Alchemy.config.user_class.respond_to?(:where)
|
|
20
20
|
|
|
21
21
|
where(user: user, folded: true)
|
|
22
22
|
end
|
|
@@ -110,6 +110,25 @@ module Alchemy
|
|
|
110
110
|
element.ingredient_definition_for(role) || IngredientDefinition.new
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
+
# Returns the translated role for displaying in labels
|
|
114
|
+
#
|
|
115
|
+
# Translate it in your locale yml file:
|
|
116
|
+
#
|
|
117
|
+
# alchemy:
|
|
118
|
+
# ingredient_roles:
|
|
119
|
+
# foo: Bar
|
|
120
|
+
#
|
|
121
|
+
# Optionally you can scope your ingredient role to an element:
|
|
122
|
+
#
|
|
123
|
+
# alchemy:
|
|
124
|
+
# ingredient_roles:
|
|
125
|
+
# article:
|
|
126
|
+
# foo: Baz
|
|
127
|
+
#
|
|
128
|
+
def translated_role
|
|
129
|
+
self.class.translated_label_for(role, element&.name)
|
|
130
|
+
end
|
|
131
|
+
|
|
113
132
|
# The first 30 characters of the value
|
|
114
133
|
#
|
|
115
134
|
# Used by the Element#preview_text method.
|
|
@@ -141,6 +160,10 @@ module Alchemy
|
|
|
141
160
|
false
|
|
142
161
|
end
|
|
143
162
|
|
|
163
|
+
def linked?
|
|
164
|
+
link.try(:present?)
|
|
165
|
+
end
|
|
166
|
+
|
|
144
167
|
# @return [Boolean]
|
|
145
168
|
def preview_ingredient?
|
|
146
169
|
!!definition.as_element_title
|
|
@@ -154,10 +177,24 @@ module Alchemy
|
|
|
154
177
|
view_component_class.new(self, **options, html_options: html_options)
|
|
155
178
|
end
|
|
156
179
|
|
|
180
|
+
# The editor component of the ingredient.
|
|
181
|
+
#
|
|
182
|
+
def as_editor_component
|
|
183
|
+
editor_component_class.new(self)
|
|
184
|
+
end
|
|
185
|
+
|
|
157
186
|
private
|
|
158
187
|
|
|
159
188
|
def view_component_class
|
|
160
|
-
@_view_component_class ||= "
|
|
189
|
+
@_view_component_class ||= component_class_name(part: "View").constantize
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def editor_component_class
|
|
193
|
+
@_editor_component_class ||= component_class_name(part: "Editor").constantize
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def component_class_name(part:)
|
|
197
|
+
"#{self.class.name}#{part}"
|
|
161
198
|
end
|
|
162
199
|
|
|
163
200
|
def set_default_value
|
|
@@ -4,7 +4,6 @@ module Alchemy
|
|
|
4
4
|
class IngredientDefinition
|
|
5
5
|
include ActiveModel::Model
|
|
6
6
|
include ActiveModel::Attributes
|
|
7
|
-
include Alchemy::Hints
|
|
8
7
|
|
|
9
8
|
extend ActiveModel::Translation
|
|
10
9
|
|
|
@@ -19,6 +18,10 @@ module Alchemy
|
|
|
19
18
|
attribute :hint
|
|
20
19
|
attribute :searchable, :boolean, default: true
|
|
21
20
|
|
|
21
|
+
# Needs to be down here in order to have the attribute reader
|
|
22
|
+
# available after the attribute is defined.
|
|
23
|
+
include Alchemy::Hints
|
|
24
|
+
|
|
22
25
|
validates :role,
|
|
23
26
|
presence: true,
|
|
24
27
|
format: {
|
|
@@ -80,6 +80,8 @@ module Alchemy
|
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def validate_format(format)
|
|
83
|
+
return if ingredient.value.blank?
|
|
84
|
+
|
|
83
85
|
matcher = if format.is_a?(String) || format.is_a?(Symbol)
|
|
84
86
|
Alchemy.config.format_matchers.get(format)
|
|
85
87
|
else
|
|
@@ -91,6 +93,8 @@ module Alchemy
|
|
|
91
93
|
end
|
|
92
94
|
|
|
93
95
|
def validate_length(opts = {})
|
|
96
|
+
return if ingredient.value.blank?
|
|
97
|
+
|
|
94
98
|
value_length = ingredient.value.to_s.length
|
|
95
99
|
if value_length < opts[:minimum]
|
|
96
100
|
ingredient.errors.add(:value, :too_short, count: opts[:minimum])
|
|
@@ -103,10 +107,10 @@ module Alchemy
|
|
|
103
107
|
def duplicates
|
|
104
108
|
ingredient.class
|
|
105
109
|
.joins(:element, :page_version, :language)
|
|
106
|
-
.merge(Alchemy::PageVersion.
|
|
110
|
+
.merge(Alchemy::PageVersion.draft)
|
|
107
111
|
.merge(Alchemy::Language.where(id: ingredient.language.id))
|
|
108
112
|
.where(Alchemy::Element.table_name => {name: ingredient.element.name})
|
|
109
|
-
.where(value
|
|
113
|
+
.where(ingredient.class.arel_table[:value].eq(ingredient.value_before_type_cast))
|
|
110
114
|
.where.not(id: ingredient.id)
|
|
111
115
|
end
|
|
112
116
|
end
|
|
@@ -24,23 +24,6 @@ module Alchemy
|
|
|
24
24
|
"H#{level}: #{value}"[0..maxlength - 1]
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
def level_options
|
|
28
|
-
levels.map { |level| ["H#{level}", level] }
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def size_options
|
|
32
|
-
sizes.map do |size|
|
|
33
|
-
case size
|
|
34
|
-
when Array
|
|
35
|
-
size
|
|
36
|
-
else
|
|
37
|
-
[".h#{size}", size]
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
private
|
|
43
|
-
|
|
44
27
|
def levels
|
|
45
28
|
settings.fetch(:levels, 1..6)
|
|
46
29
|
end
|
|
@@ -49,6 +32,8 @@ module Alchemy
|
|
|
49
32
|
settings.fetch(:sizes, [])
|
|
50
33
|
end
|
|
51
34
|
|
|
35
|
+
private
|
|
36
|
+
|
|
52
37
|
def set_level_and_size
|
|
53
38
|
self.level ||= levels.first
|
|
54
39
|
self.size ||= sizes.first
|
|
@@ -38,10 +38,10 @@ module Alchemy
|
|
|
38
38
|
upsample
|
|
39
39
|
]
|
|
40
40
|
|
|
41
|
+
delegate :description_for, :name, to: :picture, allow_nil: true
|
|
42
|
+
|
|
41
43
|
def alt_text(language: Alchemy::Current.language)
|
|
42
|
-
alt_tag.presence ||
|
|
43
|
-
picture&.description_for(language) ||
|
|
44
|
-
picture&.name&.humanize
|
|
44
|
+
alt_tag.presence || description_for(language) || name&.humanize
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
# The first 30 characters of the pictures name
|
|
@@ -51,7 +51,7 @@ module Alchemy
|
|
|
51
51
|
# @param [Integer] max_length (30)
|
|
52
52
|
#
|
|
53
53
|
def preview_text(max_length = 30)
|
|
54
|
-
|
|
54
|
+
name.to_s[0..max_length - 1]
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
# The picture view component with mapped options.
|
|
@@ -6,6 +6,25 @@ module Alchemy
|
|
|
6
6
|
#
|
|
7
7
|
class Select < Alchemy::Ingredient
|
|
8
8
|
allow_settings %i[display_inline select_values]
|
|
9
|
+
allow_settings %i[display_inline select_values multiple]
|
|
10
|
+
|
|
11
|
+
serialize :value, coder: JSON
|
|
12
|
+
|
|
13
|
+
# Override value getter to handle multiple selection
|
|
14
|
+
def value
|
|
15
|
+
val = self[:value] || []
|
|
16
|
+
multiple? ? val : val.first
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Override value setter to handle multiple selection
|
|
20
|
+
def value=(new_value)
|
|
21
|
+
super(Array(new_value).compact_blank)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Check if multiple selection is enabled in settings
|
|
25
|
+
def multiple?
|
|
26
|
+
settings[:multiple] == true
|
|
27
|
+
end
|
|
9
28
|
end
|
|
10
29
|
end
|
|
11
30
|
end
|
data/app/models/alchemy/node.rb
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
module Alchemy
|
|
4
4
|
class Node < BaseRecord
|
|
5
5
|
VALID_URL_REGEX = /\A(\/|\D[a-z+\d.-]+:)/
|
|
6
|
+
SKIPPED_ATTRIBUTES_ON_COPY = %w[id created_at updated_at creator_id updater_id lft rgt depth parent_id]
|
|
6
7
|
|
|
7
8
|
before_destroy :check_if_related_node_ingredients_present
|
|
8
9
|
|
|
9
10
|
acts_as_nested_set scope: "language_id", touch: true
|
|
10
|
-
stampable stamper_class_name: Alchemy.user_class_name
|
|
11
|
+
stampable stamper_class_name: Alchemy.config.user_class_name
|
|
11
12
|
|
|
12
13
|
belongs_to :language, class_name: "Alchemy::Language"
|
|
13
14
|
belongs_to :page, class_name: "Alchemy::Page", optional: true, inverse_of: :nodes
|
|
@@ -46,6 +47,32 @@ module Alchemy
|
|
|
46
47
|
read_definitions_file
|
|
47
48
|
end
|
|
48
49
|
|
|
50
|
+
def all_from_clipboard(clipboard)
|
|
51
|
+
return [] if clipboard.blank?
|
|
52
|
+
|
|
53
|
+
where(id: clipboard.collect { |n| n["id"] })
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def copy_and_paste(source, new_parent, new_name)
|
|
57
|
+
# Prevent pasting a node as a child of itself or any of its descendants
|
|
58
|
+
return if new_parent && source.is_or_is_ancestor_of?(new_parent)
|
|
59
|
+
|
|
60
|
+
attributes = source.attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY).merge(
|
|
61
|
+
name: new_name,
|
|
62
|
+
parent: new_parent,
|
|
63
|
+
language: new_parent&.language || source.language
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
node = create!(attributes)
|
|
67
|
+
|
|
68
|
+
# Copy all descendants
|
|
69
|
+
source.children.each do |child|
|
|
70
|
+
copy_and_paste(child, node, child.name)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
node
|
|
74
|
+
end
|
|
75
|
+
|
|
49
76
|
private
|
|
50
77
|
|
|
51
78
|
def searchable_alchemy_resource_associations
|
|
@@ -19,9 +19,6 @@ module Alchemy
|
|
|
19
19
|
uniqueness: {scope: [:language_id, :layoutpage], if: -> { urlname.present? }, case_sensitive: false},
|
|
20
20
|
exclusion: {in: RESERVED_URLNAMES}
|
|
21
21
|
|
|
22
|
-
before_save :set_title,
|
|
23
|
-
if: -> { title.blank? }
|
|
24
|
-
|
|
25
22
|
after_update :update_descendants_urlnames,
|
|
26
23
|
if: :saved_change_to_urlname?
|
|
27
24
|
|
|
@@ -69,10 +66,6 @@ module Alchemy
|
|
|
69
66
|
self[:urlname] = nested_url_name
|
|
70
67
|
end
|
|
71
68
|
|
|
72
|
-
def set_title
|
|
73
|
-
self[:title] = name
|
|
74
|
-
end
|
|
75
|
-
|
|
76
69
|
# Returns the full nested urlname.
|
|
77
70
|
#
|
|
78
71
|
def nested_url_name
|
|
@@ -35,9 +35,13 @@ module Alchemy
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def folded?(user_id)
|
|
38
|
-
return unless Alchemy.user_class < ActiveRecord::Base
|
|
38
|
+
return unless Alchemy.config.user_class < ActiveRecord::Base
|
|
39
39
|
|
|
40
|
-
folded_pages.
|
|
40
|
+
if folded_pages.loaded?
|
|
41
|
+
folded_pages.any? { |p| p.folded && p.user_id == user_id }
|
|
42
|
+
else
|
|
43
|
+
folded_pages.where(user_id: user_id, folded: true).any?
|
|
44
|
+
end
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
# Returns an Array of Alchemy roles which are able to edit this template
|
|
@@ -95,7 +99,7 @@ module Alchemy
|
|
|
95
99
|
def definition
|
|
96
100
|
definition = PageDefinition.get(page_layout)
|
|
97
101
|
if definition.nil?
|
|
98
|
-
|
|
102
|
+
Logger.warn "Page definition for '#{page_layout}' not found. Please check page_layouts.yml file."
|
|
99
103
|
return PageDefinition.new
|
|
100
104
|
end
|
|
101
105
|
definition
|
|
@@ -51,7 +51,7 @@ module Alchemy
|
|
|
51
51
|
-> {
|
|
52
52
|
joins(:language, :versions)
|
|
53
53
|
.merge(Language.published)
|
|
54
|
-
.merge(PageVersion.
|
|
54
|
+
.merge(PageVersion.published)
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
# All pages that are a published language root
|
|
@@ -78,6 +78,18 @@ module Alchemy
|
|
|
78
78
|
#
|
|
79
79
|
scope :contentpages, -> { where(layoutpage: [false, nil]) }
|
|
80
80
|
|
|
81
|
+
# Returns all public contentpages that are not locked.
|
|
82
|
+
#
|
|
83
|
+
# Used for flushing all pages caches at once.
|
|
84
|
+
#
|
|
85
|
+
scope :flushables, -> { not_locked.published.contentpages }
|
|
86
|
+
|
|
87
|
+
# Returns all layoutpages that are not locked.
|
|
88
|
+
#
|
|
89
|
+
# Used for flushing all pages caches at once.
|
|
90
|
+
#
|
|
91
|
+
scope :flushable_layoutpages, -> { not_locked.layoutpages }
|
|
92
|
+
|
|
81
93
|
# All searchable pages
|
|
82
94
|
#
|
|
83
95
|
scope :searchables, -> { not_restricted.published.contentpages }
|