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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/brakeman-analysis.yml +26 -26
  3. data/.github/workflows/ci.yml +1 -1
  4. data/.github/workflows/stale.yml +2 -0
  5. data/CHANGELOG.md +45 -0
  6. data/Gemfile +2 -2
  7. data/alchemy_cms.gemspec +1 -1
  8. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +13 -11
  9. data/app/assets/stylesheets/alchemy/_variables.scss +9 -4
  10. data/app/assets/stylesheets/alchemy/elements.scss +96 -4
  11. data/app/assets/stylesheets/alchemy/forms.scss +59 -17
  12. data/app/assets/stylesheets/tinymce/skins/alchemy/content.min.css.scss +3 -3
  13. data/app/controllers/alchemy/admin/ingredients_controller.rb +1 -1
  14. data/app/controllers/alchemy/admin/pages_controller.rb +3 -3
  15. data/app/controllers/alchemy/api/ingredients_controller.rb +22 -0
  16. data/app/controllers/alchemy/messages_controller.rb +1 -1
  17. data/app/decorators/alchemy/ingredient_editor.rb +4 -0
  18. data/app/helpers/alchemy/url_helper.rb +0 -8
  19. data/app/models/alchemy/element/definitions.rb +16 -2
  20. data/app/models/alchemy/element/dom_id.rb +30 -0
  21. data/app/models/alchemy/element/element_contents.rb +39 -0
  22. data/app/models/alchemy/element/element_essences.rb +9 -1
  23. data/app/models/alchemy/element/element_ingredients.rb +7 -0
  24. data/app/models/alchemy/element/presenters.rb +1 -1
  25. data/app/models/alchemy/element.rb +14 -0
  26. data/app/models/alchemy/ingredients/headline.rb +4 -1
  27. data/app/models/alchemy/ingredients/text.rb +3 -0
  28. data/app/models/alchemy/page/page_layouts.rb +128 -0
  29. data/app/models/alchemy/page.rb +5 -2
  30. data/app/models/alchemy/picture_thumb/create.rb +15 -3
  31. data/app/models/concerns/alchemy/dom_ids.rb +32 -0
  32. data/app/serializers/alchemy/ingredient_serializer.rb +11 -0
  33. data/app/views/alchemy/admin/elements/update.js.erb +3 -0
  34. data/app/views/alchemy/admin/ingredients/_dom_id_fields.html.erb +4 -0
  35. data/app/views/alchemy/admin/ingredients/_headline_fields.html.erb +3 -0
  36. data/app/views/alchemy/admin/ingredients/_text_fields.html.erb +3 -0
  37. data/app/views/alchemy/admin/ingredients/update.js.erb +7 -0
  38. data/app/views/alchemy/admin/languages/_form.html.erb +1 -1
  39. data/app/views/alchemy/admin/languages/_language.html.erb +1 -1
  40. data/app/views/alchemy/admin/pages/_form.html.erb +9 -0
  41. data/app/views/alchemy/admin/pages/_page_layout_filter.html.erb +1 -1
  42. data/app/views/alchemy/admin/partials/_routes.html.erb +2 -1
  43. data/app/views/alchemy/ingredients/_headline_editor.html.erb +22 -20
  44. data/app/views/alchemy/ingredients/_headline_view.html.erb +1 -0
  45. data/app/views/alchemy/ingredients/_text_editor.html.erb +3 -0
  46. data/app/views/alchemy/ingredients/_text_view.html.erb +7 -3
  47. data/app/views/alchemy/ingredients/shared/_anchor.html.erb +9 -0
  48. data/app/views/alchemy/messages_mailer/contact_form_mail.de.text.erb +2 -0
  49. data/app/views/alchemy/messages_mailer/contact_form_mail.en.text.erb +2 -0
  50. data/app/views/alchemy/messages_mailer/contact_form_mail.es.text.erb +2 -0
  51. data/config/locales/alchemy.en.yml +7 -2
  52. data/config/routes.rb +1 -0
  53. data/db/migrate/20230123112425_add_searchable_to_alchemy_pages.rb +9 -0
  54. data/lib/alchemy/deprecation.rb +1 -1
  55. data/lib/alchemy/error_tracking/error_logger.rb +13 -0
  56. data/lib/alchemy/error_tracking.rb +3 -1
  57. data/lib/alchemy/page_layout.rb +0 -113
  58. data/lib/alchemy/test_support/shared_dom_ids_examples.rb +119 -0
  59. data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +7 -5
  60. data/lib/alchemy/version.rb +1 -1
  61. data/lib/alchemy.rb +17 -0
  62. data/package/admin.js +2 -0
  63. data/package/src/ingredient_anchor_link.js +17 -0
  64. data/package.json +2 -2
  65. data/vendor/assets/javascripts/tinymce/tinymce.min.js +2 -2
  66. 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
- ElementDefinition.all
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
- ElementDefinition.get(name)
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 <<~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|
@@ -82,7 +82,7 @@ module Alchemy
82
82
  # Returns a dom id used for elements html id tag.
83
83
  #
84
84
  def dom_id
85
- [parent_element&.dom_id, name, position].compact.join("-")
85
+ self.class.dom_id_class.new(self).call
86
86
  end
87
87
 
88
88
  # The content that's used for element's preview text.
@@ -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| ["H#{size}", size] }
26
+ sizes.map { |size| [".h#{size}", size] }
24
27
  end
25
28
 
26
29
  private
@@ -7,7 +7,10 @@ module Alchemy
7
7
  # Optionally it can have a link
8
8
  #
9
9
  class Text < Alchemy::Ingredient
10
+ include DomIds
11
+
10
12
  store_accessor :data,
13
+ :dom_id,
11
14
  :link,
12
15
  :link_target,
13
16
  :link_title,
@@ -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
@@ -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, layoutpage = false)
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::PageLayout.selectable_layouts(language_id, layoutpage)
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
- image = variant.image
19
- image.to_file(server_path(uid)).close
20
- variant.picture.thumbs.create!(
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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class IngredientSerializer < ActiveModel::Serializer
5
+ attributes :id,
6
+ :role,
7
+ :value,
8
+ :element_id,
9
+ :data
10
+ end
11
+ 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 %>);
@@ -0,0 +1,4 @@
1
+ <% disabled = ingredient.settings[:anchor].to_s != "true" %>
2
+ <%= f.input :dom_id,
3
+ disabled: disabled,
4
+ hint: disabled && Alchemy.t(:automatic_anchor_notice) %>
@@ -0,0 +1,3 @@
1
+ <% if ingredient.settings[:anchor] %>
2
+ <%= render "dom_id_fields", f: f, ingredient: ingredient %>
3
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <% if ingredient.settings[:anchor] %>
2
+ <%= render "dom_id_fields", f: f, ingredient: ingredient %>
3
+ <% end %>
@@ -0,0 +1,7 @@
1
+ Alchemy.closeCurrentDialog(
2
+ <% if @ingredient.settings[:anchor] %>
3
+ function() {
4
+ Alchemy.IngredientAnchorLink.updateIcon(<%= @ingredient.id %>, <%= @ingredient.dom_id.present? %>);
5
+ }
6
+ <% end %>
7
+ );
@@ -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::PageLayout.human_layout_name(p['name']) },
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::PageLayout.human_layout_name(language.page_layout) %>
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">
@@ -6,7 +6,7 @@
6
6
  options_for_select(
7
7
  @current_language.site.page_layout_names.map do |layout|
8
8
  [
9
- Alchemy::PageLayout.human_layout_name(layout),
9
+ Alchemy::Page.human_layout_name(layout),
10
10
  layout
11
11
  ]
12
12
  end,