alchemy_cms 6.0.12 → 6.1.1
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.
Potentially problematic release.
This version of alchemy_cms might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.github/workflows/brakeman-analysis.yml +26 -26
- data/.github/workflows/ci.yml +1 -1
- data/.github/workflows/stale.yml +2 -0
- data/CHANGELOG.md +45 -0
- data/Gemfile +2 -2
- data/alchemy_cms.gemspec +1 -1
- data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +13 -11
- data/app/assets/stylesheets/alchemy/_variables.scss +9 -4
- data/app/assets/stylesheets/alchemy/elements.scss +96 -4
- data/app/assets/stylesheets/alchemy/forms.scss +59 -17
- data/app/assets/stylesheets/tinymce/skins/alchemy/content.min.css.scss +3 -3
- data/app/controllers/alchemy/admin/ingredients_controller.rb +1 -1
- data/app/controllers/alchemy/admin/pages_controller.rb +3 -3
- data/app/controllers/alchemy/api/ingredients_controller.rb +22 -0
- data/app/controllers/alchemy/messages_controller.rb +1 -1
- data/app/decorators/alchemy/ingredient_editor.rb +4 -0
- data/app/helpers/alchemy/url_helper.rb +0 -8
- data/app/models/alchemy/element/definitions.rb +16 -2
- data/app/models/alchemy/element/dom_id.rb +30 -0
- data/app/models/alchemy/element/element_contents.rb +39 -0
- data/app/models/alchemy/element/element_essences.rb +9 -1
- data/app/models/alchemy/element/element_ingredients.rb +7 -0
- data/app/models/alchemy/element/presenters.rb +1 -1
- data/app/models/alchemy/element.rb +14 -0
- data/app/models/alchemy/ingredients/headline.rb +4 -1
- data/app/models/alchemy/ingredients/text.rb +3 -0
- data/app/models/alchemy/page/page_layouts.rb +128 -0
- data/app/models/alchemy/page.rb +5 -2
- data/app/models/alchemy/picture_thumb/create.rb +15 -3
- data/app/models/concerns/alchemy/dom_ids.rb +32 -0
- data/app/serializers/alchemy/ingredient_serializer.rb +11 -0
- data/app/views/alchemy/admin/elements/update.js.erb +3 -0
- data/app/views/alchemy/admin/ingredients/_dom_id_fields.html.erb +4 -0
- data/app/views/alchemy/admin/ingredients/_headline_fields.html.erb +3 -0
- data/app/views/alchemy/admin/ingredients/_text_fields.html.erb +3 -0
- data/app/views/alchemy/admin/ingredients/update.js.erb +7 -0
- data/app/views/alchemy/admin/languages/_form.html.erb +1 -1
- data/app/views/alchemy/admin/languages/_language.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_form.html.erb +9 -0
- data/app/views/alchemy/admin/pages/_page_layout_filter.html.erb +1 -1
- data/app/views/alchemy/admin/partials/_routes.html.erb +2 -1
- data/app/views/alchemy/ingredients/_headline_editor.html.erb +22 -20
- data/app/views/alchemy/ingredients/_headline_view.html.erb +1 -0
- data/app/views/alchemy/ingredients/_text_editor.html.erb +3 -0
- data/app/views/alchemy/ingredients/_text_view.html.erb +7 -3
- data/app/views/alchemy/ingredients/shared/_anchor.html.erb +9 -0
- data/app/views/alchemy/messages_mailer/contact_form_mail.de.text.erb +2 -0
- data/app/views/alchemy/messages_mailer/contact_form_mail.en.text.erb +2 -0
- data/app/views/alchemy/messages_mailer/contact_form_mail.es.text.erb +2 -0
- data/config/locales/alchemy.en.yml +7 -2
- data/config/routes.rb +1 -0
- data/db/migrate/20230123112425_add_searchable_to_alchemy_pages.rb +9 -0
- data/lib/alchemy/deprecation.rb +1 -1
- data/lib/alchemy/error_tracking/error_logger.rb +13 -0
- data/lib/alchemy/error_tracking.rb +3 -1
- data/lib/alchemy/page_layout.rb +0 -113
- data/lib/alchemy/test_support/shared_dom_ids_examples.rb +119 -0
- data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +7 -5
- data/lib/alchemy/version.rb +1 -1
- data/lib/alchemy.rb +17 -0
- data/package/admin.js +2 -0
- data/package/src/ingredient_anchor_link.js +17 -0
- data/package.json +2 -2
- data/vendor/assets/javascripts/tinymce/tinymce.min.js +2 -2
- metadata +19 -5
@@ -8,19 +8,33 @@ module Alchemy
|
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
10
|
module ClassMethods
|
11
|
+
# Register a custom element definitions repository
|
12
|
+
#
|
13
|
+
# The default repository is Alchemy::ElementDefinition
|
14
|
+
#
|
15
|
+
def definitions_repository=(klass)
|
16
|
+
@_definitions_repository = klass
|
17
|
+
end
|
18
|
+
|
11
19
|
# Returns the definitions from elements.yml file.
|
12
20
|
#
|
13
21
|
# Place a +elements.yml+ file inside your apps +config/alchemy+ folder to define
|
14
22
|
# your own set of elements
|
15
23
|
#
|
16
24
|
def definitions
|
17
|
-
|
25
|
+
definitions_repository.all
|
18
26
|
end
|
19
27
|
|
20
28
|
# Returns one element definition by given name.
|
21
29
|
#
|
22
30
|
def definition_by_name(name)
|
23
|
-
|
31
|
+
definitions_repository.get(name)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def definitions_repository
|
37
|
+
@_definitions_repository ||= ElementDefinition
|
24
38
|
end
|
25
39
|
end
|
26
40
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Alchemy
|
4
|
+
# Returns a dom id used for elements html id tag.
|
5
|
+
#
|
6
|
+
# Uses the elements name and its position on the page.
|
7
|
+
# If the element is nested in a parent element it prefixes
|
8
|
+
# the id with the parent elements dom_id.
|
9
|
+
#
|
10
|
+
# Register your own dom id class with
|
11
|
+
#
|
12
|
+
# Alchemy::Element.dom_id_class = MyDomIdClass
|
13
|
+
#
|
14
|
+
class Element < BaseRecord
|
15
|
+
class DomId
|
16
|
+
def initialize(element)
|
17
|
+
@element = element
|
18
|
+
@parent_element = element.parent_element
|
19
|
+
end
|
20
|
+
|
21
|
+
def call
|
22
|
+
[parent_element&.dom_id, element.name, element.position].compact.join("-")
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :element, :parent_element
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -6,29 +6,41 @@ module Alchemy
|
|
6
6
|
#
|
7
7
|
module ElementContents
|
8
8
|
# Find first content from element by given name.
|
9
|
+
# @deprecated
|
9
10
|
def content_by_name(name)
|
10
11
|
contents_by_name(name).first
|
11
12
|
end
|
12
13
|
|
14
|
+
deprecate content_by_name: :ingredient_by_role, deprecator: Alchemy::Deprecation
|
15
|
+
|
13
16
|
# Find first content from element by given essence type.
|
17
|
+
# @deprecated
|
14
18
|
def content_by_type(essence_type)
|
15
19
|
contents_by_type(essence_type).first
|
16
20
|
end
|
17
21
|
|
22
|
+
deprecate content_by_type: :ingredient_by_type, deprecator: Alchemy::Deprecation
|
23
|
+
|
18
24
|
# All contents from element by given name.
|
25
|
+
# @deprecated
|
19
26
|
def contents_by_name(name)
|
20
27
|
contents.select { |content| content.name == name.to_s }
|
21
28
|
end
|
22
29
|
|
30
|
+
deprecate contents_by_name: :ingredients_by_role, deprecator: Alchemy::Deprecation
|
31
|
+
|
23
32
|
alias_method :all_contents_by_name, :contents_by_name
|
24
33
|
|
25
34
|
# All contents from element by given essence type.
|
35
|
+
# @deprecated
|
26
36
|
def contents_by_type(essence_type)
|
27
37
|
contents.select do |content|
|
28
38
|
content.essence_type == Content.normalize_essence_type(essence_type)
|
29
39
|
end
|
30
40
|
end
|
31
41
|
|
42
|
+
deprecate contents_by_type: :ingredients_by_type, deprecator: Alchemy::Deprecation
|
43
|
+
|
32
44
|
alias_method :all_contents_by_type, :contents_by_type
|
33
45
|
|
34
46
|
# Updates all related contents by calling +update_essence+ on each of them.
|
@@ -48,6 +60,7 @@ module Alchemy
|
|
48
60
|
# "2" => {link: "https://google.com"}
|
49
61
|
# )
|
50
62
|
#
|
63
|
+
# @deprecated
|
51
64
|
def update_contents(contents_attributes)
|
52
65
|
return true if contents_attributes.nil?
|
53
66
|
|
@@ -58,13 +71,18 @@ module Alchemy
|
|
58
71
|
errors.blank?
|
59
72
|
end
|
60
73
|
|
74
|
+
deprecate :update_contents, deprecator: Alchemy::Deprecation
|
75
|
+
|
61
76
|
# Copy current content's contents to given target element
|
77
|
+
# @deprecated
|
62
78
|
def copy_contents_to(element)
|
63
79
|
contents.map do |content|
|
64
80
|
Content.copy(content, element_id: element.id)
|
65
81
|
end
|
66
82
|
end
|
67
83
|
|
84
|
+
deprecate :copy_contents_to, deprecator: Alchemy::Deprecation
|
85
|
+
|
68
86
|
# Returns the content that is marked as rss title.
|
69
87
|
#
|
70
88
|
# Mark a content as rss title in your +elements.yml+ file:
|
@@ -75,10 +93,13 @@ module Alchemy
|
|
75
93
|
# type: EssenceText
|
76
94
|
# rss_title: true
|
77
95
|
#
|
96
|
+
# @deprecated
|
78
97
|
def content_for_rss_title
|
79
98
|
content_for_rss_meta("title")
|
80
99
|
end
|
81
100
|
|
101
|
+
deprecate :content_for_rss_title, deprecator: Alchemy::Deprecation
|
102
|
+
|
82
103
|
# Returns the content that is marked as rss description.
|
83
104
|
#
|
84
105
|
# Mark a content as rss description in your +elements.yml+ file:
|
@@ -89,18 +110,25 @@ module Alchemy
|
|
89
110
|
# type: EssenceRichtext
|
90
111
|
# rss_description: true
|
91
112
|
#
|
113
|
+
# @deprecated
|
92
114
|
def content_for_rss_description
|
93
115
|
content_for_rss_meta("description")
|
94
116
|
end
|
95
117
|
|
118
|
+
deprecate :content_for_rss_description, deprecator: Alchemy::Deprecation
|
119
|
+
|
96
120
|
# Returns the array with the hashes for all element contents in the elements.yml file
|
121
|
+
# @deprecated
|
97
122
|
def content_definitions
|
98
123
|
return nil if definition.blank?
|
99
124
|
|
100
125
|
definition["contents"]
|
101
126
|
end
|
102
127
|
|
128
|
+
deprecate content_definitions: :ingredient_definitions, deprecator: Alchemy::Deprecation
|
129
|
+
|
103
130
|
# Returns the definition for given content_name
|
131
|
+
# @deprecated
|
104
132
|
def content_definition_for(content_name)
|
105
133
|
if content_definitions.blank?
|
106
134
|
log_warning "Element #{name} is missing the content definition for #{content_name}"
|
@@ -110,10 +138,13 @@ module Alchemy
|
|
110
138
|
end
|
111
139
|
end
|
112
140
|
|
141
|
+
deprecate content_definition_for: :ingredient_definition_for, deprecator: Alchemy::Deprecation
|
142
|
+
|
113
143
|
# Returns an array of all EssenceRichtext contents ids from elements
|
114
144
|
#
|
115
145
|
# This is used to re-initialize the TinyMCE editor in the element editor.
|
116
146
|
#
|
147
|
+
# @deprecated
|
117
148
|
def richtext_contents_ids
|
118
149
|
# This is not very efficient SQL wise I know, but we need to iterate
|
119
150
|
# recursivly through all descendent elements and I don't know how to do this
|
@@ -126,16 +157,24 @@ module Alchemy
|
|
126
157
|
ids.flatten
|
127
158
|
end
|
128
159
|
|
160
|
+
deprecate richtext_contents_ids: :richtext_ingredients_ids, deprecator: Alchemy::Deprecation
|
161
|
+
|
129
162
|
# True, if any of the element's contents has essence validations defined.
|
163
|
+
# @deprecated
|
130
164
|
def has_validations?
|
131
165
|
!contents.detect(&:has_validations?).blank?
|
132
166
|
end
|
133
167
|
|
168
|
+
deprecate :has_validations?, deprecator: Alchemy::Deprecation
|
169
|
+
|
134
170
|
# All element contents where the essence validation has failed.
|
171
|
+
# @deprecated
|
135
172
|
def contents_with_errors
|
136
173
|
contents.select(&:essence_validation_failed?)
|
137
174
|
end
|
138
175
|
|
176
|
+
deprecate contents_with_errors: :ingredients_with_errors, deprecator: Alchemy::Deprecation
|
177
|
+
|
139
178
|
private
|
140
179
|
|
141
180
|
def content_for_rss_meta(type)
|
@@ -7,7 +7,7 @@ module Alchemy
|
|
7
7
|
def ingredient(name)
|
8
8
|
ing = ingredient_by_role(name)
|
9
9
|
if ing
|
10
|
-
Alchemy::Deprecation.warn
|
10
|
+
Alchemy::Deprecation.warn(<<~WARN)
|
11
11
|
Using `element.ingredient` to get the value of an ingredient is deprecated and will change in Alchemy 6.1
|
12
12
|
If you want to read the value of an elements ingredient please use `element.value_for(:ingredient_role)` instead.
|
13
13
|
The next version of Alchemy will return a `Alchemy::Ingredient` record instead.
|
@@ -23,9 +23,11 @@ module Alchemy
|
|
23
23
|
|
24
24
|
# True if the element has a content for given name,
|
25
25
|
# that has an essence value (aka. ingredient) that is not blank.
|
26
|
+
# @deprecated
|
26
27
|
def has_ingredient?(name)
|
27
28
|
ingredient(name).present?
|
28
29
|
end
|
30
|
+
|
29
31
|
deprecate has_ingredient?: :has_value_for?, deprecator: Alchemy::Deprecation
|
30
32
|
|
31
33
|
# Returns all essence errors in the format of:
|
@@ -39,6 +41,7 @@ module Alchemy
|
|
39
41
|
#
|
40
42
|
# Get translated error messages with +Element#essence_error_messages+
|
41
43
|
#
|
44
|
+
# @deprecated
|
42
45
|
def essence_errors
|
43
46
|
essence_errors = {}
|
44
47
|
contents.each do |content|
|
@@ -49,6 +52,8 @@ module Alchemy
|
|
49
52
|
essence_errors
|
50
53
|
end
|
51
54
|
|
55
|
+
deprecate :essence_errors, deprecator: Alchemy::Deprecation
|
56
|
+
|
52
57
|
# Essence validation errors
|
53
58
|
#
|
54
59
|
# == Error messages are translated via I18n
|
@@ -103,6 +108,7 @@ module Alchemy
|
|
103
108
|
# invalid: %{field} has wrong format
|
104
109
|
# blank: %{field} can't be blank
|
105
110
|
#
|
111
|
+
# @deprecated
|
106
112
|
def essence_error_messages
|
107
113
|
messages = []
|
108
114
|
essence_errors.each do |content_name, errors|
|
@@ -120,6 +126,8 @@ module Alchemy
|
|
120
126
|
end
|
121
127
|
messages
|
122
128
|
end
|
129
|
+
|
130
|
+
deprecate essence_error_messages: :ingredient_error_messages, deprecator: Alchemy::Deprecation
|
123
131
|
end
|
124
132
|
end
|
125
133
|
end
|
@@ -37,6 +37,13 @@ module Alchemy
|
|
37
37
|
ingredients_by_type(type).first
|
38
38
|
end
|
39
39
|
|
40
|
+
# All ingredients from element by given role.
|
41
|
+
def ingredients_by_role(role)
|
42
|
+
ingredients.select do |ingredient|
|
43
|
+
ingredient.role == Ingredient.normalize_type(role)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
40
47
|
# All ingredients from element by given type.
|
41
48
|
def ingredients_by_type(type)
|
42
49
|
ingredients.select do |ingredient|
|
@@ -61,6 +61,8 @@ module Alchemy
|
|
61
61
|
|
62
62
|
has_many :contents, dependent: :destroy, inverse_of: :element
|
63
63
|
|
64
|
+
deprecate contents: :ingredients, deprecator: Alchemy::Deprecation
|
65
|
+
|
64
66
|
before_destroy :delete_all_nested_elements
|
65
67
|
|
66
68
|
has_many :all_nested_elements,
|
@@ -143,6 +145,18 @@ module Alchemy
|
|
143
145
|
super(element_definition.merge(element_attributes).except(*FORBIDDEN_DEFINITION_ATTRIBUTES))
|
144
146
|
end
|
145
147
|
|
148
|
+
# The class responsible for the +dom_id+ of elements.
|
149
|
+
# Defaults to +Alchemy::Element::DomId+.
|
150
|
+
def dom_id_class
|
151
|
+
@_dom_id_class || DomId
|
152
|
+
end
|
153
|
+
|
154
|
+
# Register a custom +DomId+ class responsible for the +dom_id+ of elements.
|
155
|
+
# Defaults to +Alchemy::Element::DomId+.
|
156
|
+
def dom_id_class=(klass)
|
157
|
+
@_dom_id_class = klass
|
158
|
+
end
|
159
|
+
|
146
160
|
# This methods does a copy of source and all depending contents and all of their depending essences.
|
147
161
|
#
|
148
162
|
# == Options
|
@@ -5,7 +5,10 @@ module Alchemy
|
|
5
5
|
# A text headline
|
6
6
|
#
|
7
7
|
class Headline < Alchemy::Ingredient
|
8
|
+
include DomIds
|
9
|
+
|
8
10
|
store_accessor :data,
|
11
|
+
:dom_id,
|
9
12
|
:level,
|
10
13
|
:size
|
11
14
|
|
@@ -20,7 +23,7 @@ module Alchemy
|
|
20
23
|
end
|
21
24
|
|
22
25
|
def size_options
|
23
|
-
sizes.map { |size| ["
|
26
|
+
sizes.map { |size| [".h#{size}", size] }
|
24
27
|
end
|
25
28
|
|
26
29
|
private
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Alchemy
|
4
|
+
class Page < BaseRecord
|
5
|
+
# Module concerning page layouts
|
6
|
+
#
|
7
|
+
module PageLayouts
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# Register a custom page layouts repository
|
12
|
+
#
|
13
|
+
# The default repository is Alchemy::PageLayout
|
14
|
+
#
|
15
|
+
def layouts_repository=(klass)
|
16
|
+
@_layouts_repository = klass
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns page layouts ready for Rails' select form helper.
|
20
|
+
#
|
21
|
+
def layouts_for_select(language_id, layoutpages: false)
|
22
|
+
@map_array = []
|
23
|
+
mapped_layouts_for_select(selectable_layouts(language_id, layoutpages: layoutpages))
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns page layouts including given layout ready for Rails' select form helper.
|
27
|
+
#
|
28
|
+
def layouts_with_own_for_select(page_layout_name, language_id, layoutpages: false)
|
29
|
+
layouts = selectable_layouts(language_id, layoutpages: layoutpages)
|
30
|
+
if layouts.detect { |l| l["name"] == page_layout_name }.nil?
|
31
|
+
@map_array = [[human_layout_name(page_layout_name), page_layout_name]]
|
32
|
+
else
|
33
|
+
@map_array = []
|
34
|
+
end
|
35
|
+
mapped_layouts_for_select(layouts)
|
36
|
+
end
|
37
|
+
|
38
|
+
deprecate :layouts_with_own_for_select, deprecator: Alchemy::Deprecation
|
39
|
+
|
40
|
+
# Returns all layouts that can be used for creating a new page.
|
41
|
+
#
|
42
|
+
# It removes all layouts from available layouts that are unique and already taken and that are marked as hide.
|
43
|
+
#
|
44
|
+
# @param [Fixnum]
|
45
|
+
# language_id of current used Language.
|
46
|
+
# @param [Boolean] (false)
|
47
|
+
# Pass true to only select layouts for global/layout pages.
|
48
|
+
#
|
49
|
+
def selectable_layouts(language_id, layoutpages: false)
|
50
|
+
@language_id = language_id
|
51
|
+
layouts_repository.all.select do |layout|
|
52
|
+
if layoutpages
|
53
|
+
layout["layoutpage"] && layout_available?(layout)
|
54
|
+
else
|
55
|
+
!layout["layoutpage"] && layout_available?(layout)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Translates name for given layout
|
61
|
+
#
|
62
|
+
# === Translation example
|
63
|
+
#
|
64
|
+
# en:
|
65
|
+
# alchemy:
|
66
|
+
# page_layout_names:
|
67
|
+
# products_overview: Products Overview
|
68
|
+
#
|
69
|
+
# @param [String]
|
70
|
+
# The layout name
|
71
|
+
#
|
72
|
+
def human_layout_name(layout)
|
73
|
+
Alchemy.t(layout, scope: "page_layout_names", default: layout.to_s.humanize)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def layouts_repository
|
79
|
+
@_layouts_repository ||= PageLayout
|
80
|
+
end
|
81
|
+
|
82
|
+
# Maps given layouts for Rails select form helper.
|
83
|
+
#
|
84
|
+
def mapped_layouts_for_select(layouts)
|
85
|
+
layouts.each do |layout|
|
86
|
+
@map_array << [human_layout_name(layout["name"]), layout["name"]]
|
87
|
+
end
|
88
|
+
@map_array
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns true if the given layout is unique and not already taken or it should be hidden.
|
92
|
+
#
|
93
|
+
def layout_available?(layout)
|
94
|
+
!layout["hide"] && !already_taken?(layout) && available_on_site?(layout)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns true if this layout is unique and already taken by another page.
|
98
|
+
#
|
99
|
+
def already_taken?(layout)
|
100
|
+
layout["unique"] && page_with_layout_existing?(layout["name"])
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns true if one page already has the given layout
|
104
|
+
#
|
105
|
+
def page_with_layout_existing?(layout)
|
106
|
+
Alchemy::Page.where(page_layout: layout, language_id: @language_id).pluck(:id).any?
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns true if given layout is available for current site.
|
110
|
+
#
|
111
|
+
# If no site layouts are defined it always returns true.
|
112
|
+
#
|
113
|
+
# == Example
|
114
|
+
#
|
115
|
+
# # config/alchemy/site_layouts.yml
|
116
|
+
# - name: default_site
|
117
|
+
# page_layouts: [default_intro]
|
118
|
+
#
|
119
|
+
def available_on_site?(layout)
|
120
|
+
return false unless Alchemy::Site.current
|
121
|
+
|
122
|
+
Alchemy::Site.current.definition.blank? ||
|
123
|
+
Alchemy::Site.current.definition.fetch("page_layouts", []).include?(layout["name"])
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/app/models/alchemy/page.rb
CHANGED
@@ -36,6 +36,7 @@
|
|
36
36
|
#
|
37
37
|
|
38
38
|
require_dependency "alchemy/page/fixed_attributes"
|
39
|
+
require_dependency "alchemy/page/page_layouts"
|
39
40
|
require_dependency "alchemy/page/page_scopes"
|
40
41
|
require_dependency "alchemy/page/page_natures"
|
41
42
|
require_dependency "alchemy/page/page_naming"
|
@@ -78,6 +79,7 @@ module Alchemy
|
|
78
79
|
:restricted,
|
79
80
|
:robot_index,
|
80
81
|
:robot_follow,
|
82
|
+
:searchable,
|
81
83
|
:sitemap,
|
82
84
|
:tag_list,
|
83
85
|
:title,
|
@@ -152,6 +154,7 @@ module Alchemy
|
|
152
154
|
after_update :touch_nodes
|
153
155
|
|
154
156
|
# Concerns
|
157
|
+
include PageLayouts
|
155
158
|
include PageScopes
|
156
159
|
include PageNatures
|
157
160
|
include PageNaming
|
@@ -267,11 +270,11 @@ module Alchemy
|
|
267
270
|
where(id: clipboard.collect { |p| p["id"] })
|
268
271
|
end
|
269
272
|
|
270
|
-
def all_from_clipboard_for_select(clipboard, language_id,
|
273
|
+
def all_from_clipboard_for_select(clipboard, language_id, layoutpages: false)
|
271
274
|
return [] if clipboard.blank?
|
272
275
|
|
273
276
|
clipboard_pages = all_from_clipboard(clipboard)
|
274
|
-
allowed_page_layouts = Alchemy::
|
277
|
+
allowed_page_layouts = Alchemy::Page.selectable_layouts(language_id, layoutpages: layoutpages)
|
275
278
|
allowed_page_layout_names = allowed_page_layouts.collect { |p| p["name"] }
|
276
279
|
clipboard_pages.select { |cp| allowed_page_layout_names.include?(cp.page_layout) }
|
277
280
|
end
|
@@ -15,13 +15,25 @@ module Alchemy
|
|
15
15
|
# @return [Alchemy::PictureThumb] The persisted thumbnail record
|
16
16
|
#
|
17
17
|
def call(variant, signature, uid)
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
return if !variant.picture.valid?
|
19
|
+
|
20
|
+
# create the thumb before storing
|
21
|
+
# to prevent db race conditions
|
22
|
+
thumb = Alchemy::PictureThumb.create!(
|
21
23
|
picture: variant.picture,
|
22
24
|
signature: signature,
|
23
25
|
uid: uid,
|
24
26
|
)
|
27
|
+
begin
|
28
|
+
# process the image
|
29
|
+
image = variant.image
|
30
|
+
# store the processed image
|
31
|
+
image.to_file(server_path(uid)).close
|
32
|
+
rescue RuntimeError => e
|
33
|
+
ErrorTracking.notification_handler.call(e)
|
34
|
+
# destroy the thumb if processing or storing fails
|
35
|
+
thumb&.destroy
|
36
|
+
end
|
25
37
|
end
|
26
38
|
|
27
39
|
private
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Alchemy
|
4
|
+
module DomIds
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
RESERVED_ANCHOR_SETTING_VALUES = %w[false from_value true]
|
8
|
+
|
9
|
+
included do
|
10
|
+
before_validation :parameterize_dom_id,
|
11
|
+
if: -> { settings[:anchor].to_s == "true" }
|
12
|
+
before_validation :set_dom_id_from_value,
|
13
|
+
if: -> { settings[:anchor].to_s == "from_value" }
|
14
|
+
before_validation :set_dom_id_to_fixed_value,
|
15
|
+
if: -> { !RESERVED_ANCHOR_SETTING_VALUES.include? settings[:anchor].to_s }
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def parameterize_dom_id
|
21
|
+
self.dom_id = dom_id&.parameterize
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_dom_id_from_value
|
25
|
+
self.dom_id = value&.parameterize
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_dom_id_to_fixed_value
|
29
|
+
self.dom_id = settings[:anchor]&.parameterize
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -7,6 +7,9 @@
|
|
7
7
|
|
8
8
|
$errors.hide();
|
9
9
|
$el.trigger('SaveElement.Alchemy', {previewText: '<%= j sanitize(@element.preview_text) %>'});
|
10
|
+
<% @element.ingredients.select { |i| i.settings[:anchor] }.each do |ingredient| %>
|
11
|
+
Alchemy.IngredientAnchorLink.updateIcon(<%= ingredient.id %>, <%= ingredient.dom_id.present? %>);
|
12
|
+
<% end %>
|
10
13
|
Alchemy.growl('<%= Alchemy.t(:element_saved) %>');
|
11
14
|
Alchemy.PreviewWindow.refresh(function() {
|
12
15
|
Alchemy.ElementEditors.focusElementPreview(<%= @element.id %>);
|
@@ -14,7 +14,7 @@
|
|
14
14
|
<%= f.input :frontpage_name %>
|
15
15
|
<%= f.input :page_layout,
|
16
16
|
collection: Alchemy::PageLayout.all,
|
17
|
-
label_method: ->(p) { Alchemy::
|
17
|
+
label_method: ->(p) { Alchemy::Page.human_layout_name(p['name']) },
|
18
18
|
value_method: ->(p) { p['name'] },
|
19
19
|
input_html: {class: 'alchemy_selectbox'} %>
|
20
20
|
<%= f.input :public %>
|
@@ -18,7 +18,7 @@
|
|
18
18
|
<%= language.frontpage_name %>
|
19
19
|
</td>
|
20
20
|
<td>
|
21
|
-
<%= Alchemy::
|
21
|
+
<%= Alchemy::Page.human_layout_name(language.page_layout) %>
|
22
22
|
</td>
|
23
23
|
<td class="center">
|
24
24
|
<%= language.public? ? render_icon(:check) : nil %>
|
@@ -19,6 +19,15 @@
|
|
19
19
|
<%= f.input :title,
|
20
20
|
input_html: {'data-alchemy-char-counter' => 60} %>
|
21
21
|
|
22
|
+
<% if Alchemy.enable_searchable %>
|
23
|
+
<div class="input check_boxes">
|
24
|
+
<label class="control-label"><%= Alchemy.t(:fulltext_search) %></label>
|
25
|
+
<div class="control_group">
|
26
|
+
<%= page_status_checkbox(@page, :searchable) %>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
<% end %>
|
30
|
+
|
22
31
|
<div class="input check_boxes">
|
23
32
|
<label class="control-label"><%= Alchemy.t(:search_engines) %></label>
|
24
33
|
<div class="control_group">
|