alchemy_cms 6.0.12 → 6.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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">
|