alchemy_cms 6.0.14 → 6.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.

Potentially problematic release.


This version of alchemy_cms might be problematic. Click here for more details.

Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +0 -3
  3. data/.github/workflows/stale.yml +2 -0
  4. data/CHANGELOG.md +34 -5
  5. data/Gemfile +2 -2
  6. data/alchemy_cms.gemspec +2 -1
  7. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +13 -11
  8. data/app/assets/stylesheets/alchemy/_variables.scss +9 -4
  9. data/app/assets/stylesheets/alchemy/elements.scss +96 -4
  10. data/app/assets/stylesheets/alchemy/forms.scss +59 -17
  11. data/app/assets/stylesheets/tinymce/skins/alchemy/content.min.css.scss +3 -3
  12. data/app/controllers/alchemy/admin/ingredients_controller.rb +1 -1
  13. data/app/controllers/alchemy/admin/pages_controller.rb +3 -3
  14. data/app/controllers/alchemy/api/ingredients_controller.rb +22 -0
  15. data/app/controllers/alchemy/messages_controller.rb +1 -1
  16. data/app/controllers/alchemy/pages_controller.rb +19 -5
  17. data/app/decorators/alchemy/ingredient_editor.rb +4 -0
  18. data/app/helpers/alchemy/elements_helper.rb +0 -8
  19. data/app/helpers/alchemy/url_helper.rb +0 -8
  20. data/app/models/alchemy/element/definitions.rb +16 -2
  21. data/app/models/alchemy/element/dom_id.rb +30 -0
  22. data/app/models/alchemy/element/presenters.rb +1 -1
  23. data/app/models/alchemy/element.rb +12 -3
  24. data/app/models/alchemy/ingredients/headline.rb +4 -1
  25. data/app/models/alchemy/ingredients/text.rb +3 -0
  26. data/app/models/alchemy/page/page_layouts.rb +128 -0
  27. data/app/models/alchemy/page.rb +4 -2
  28. data/app/models/alchemy/picture_thumb/create.rb +1 -1
  29. data/app/models/concerns/alchemy/dom_ids.rb +32 -0
  30. data/app/serializers/alchemy/ingredient_serializer.rb +11 -0
  31. data/app/views/alchemy/admin/elements/update.js.erb +3 -0
  32. data/app/views/alchemy/admin/ingredients/_dom_id_fields.html.erb +4 -0
  33. data/app/views/alchemy/admin/ingredients/_headline_fields.html.erb +3 -0
  34. data/app/views/alchemy/admin/ingredients/_text_fields.html.erb +3 -0
  35. data/app/views/alchemy/admin/ingredients/update.js.erb +7 -0
  36. data/app/views/alchemy/admin/languages/_form.html.erb +1 -1
  37. data/app/views/alchemy/admin/languages/_language.html.erb +1 -1
  38. data/app/views/alchemy/admin/pages/_page_layout_filter.html.erb +1 -1
  39. data/app/views/alchemy/admin/partials/_routes.html.erb +2 -1
  40. data/app/views/alchemy/ingredients/_headline_editor.html.erb +22 -20
  41. data/app/views/alchemy/ingredients/_headline_view.html.erb +1 -0
  42. data/app/views/alchemy/ingredients/_text_editor.html.erb +3 -0
  43. data/app/views/alchemy/ingredients/_text_view.html.erb +7 -3
  44. data/app/views/alchemy/ingredients/shared/_anchor.html.erb +9 -0
  45. data/app/views/alchemy/messages_mailer/contact_form_mail.de.text.erb +2 -0
  46. data/app/views/alchemy/messages_mailer/contact_form_mail.en.text.erb +2 -0
  47. data/app/views/alchemy/messages_mailer/contact_form_mail.es.text.erb +2 -0
  48. data/config/locales/alchemy.en.yml +5 -2
  49. data/config/routes.rb +1 -0
  50. data/lib/alchemy/error_tracking/error_logger.rb +13 -0
  51. data/lib/alchemy/error_tracking.rb +3 -1
  52. data/lib/alchemy/install/tasks.rb +1 -7
  53. data/lib/alchemy/page_layout.rb +0 -113
  54. data/lib/alchemy/test_support/shared_dom_ids_examples.rb +119 -0
  55. data/lib/alchemy/upgrader/tasks/add_page_versions.rb +2 -2
  56. data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +7 -5
  57. data/lib/alchemy/version.rb +1 -1
  58. data/lib/alchemy.rb +4 -0
  59. data/lib/alchemy_cms.rb +1 -1
  60. data/lib/generators/alchemy/elements/templates/view.html.erb +1 -1
  61. data/lib/generators/alchemy/elements/templates/view.html.haml +1 -1
  62. data/lib/generators/alchemy/elements/templates/view.html.slim +1 -1
  63. data/lib/tasks/alchemy/thumbnails.rake +1 -1
  64. data/package/admin.js +2 -0
  65. data/package/src/ingredient_anchor_link.js +17 -0
  66. data/package.json +2 -2
  67. data/vendor/assets/javascripts/tinymce/tinymce.min.js +2 -2
  68. metadata +32 -6
  69. data/lib/non_stupid_digest_assets.rb +0 -55
@@ -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
@@ -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.
@@ -103,7 +103,6 @@ module Alchemy
103
103
  scope :published, -> { where(public: true) }
104
104
  scope :hidden, -> { where(public: false) }
105
105
  scope :not_restricted, -> { joins(:page).merge(Page.not_restricted) }
106
- scope :available, -> { published }
107
106
  scope :named, ->(names) { where(name: names) }
108
107
  scope :excluded, ->(names) { where.not(name: names) }
109
108
  scope :fixed, -> { where(fixed: true) }
@@ -143,6 +142,18 @@ module Alchemy
143
142
  super(element_definition.merge(element_attributes).except(*FORBIDDEN_DEFINITION_ATTRIBUTES))
144
143
  end
145
144
 
145
+ # The class responsible for the +dom_id+ of elements.
146
+ # Defaults to +Alchemy::Element::DomId+.
147
+ def dom_id_class
148
+ @_dom_id_class || DomId
149
+ end
150
+
151
+ # Register a custom +DomId+ class responsible for the +dom_id+ of elements.
152
+ # Defaults to +Alchemy::Element::DomId+.
153
+ def dom_id_class=(klass)
154
+ @_dom_id_class = klass
155
+ end
156
+
146
157
  # This methods does a copy of source and all depending contents and all of their depending essences.
147
158
  #
148
159
  # == Options
@@ -178,8 +189,6 @@ module Alchemy
178
189
 
179
190
  all_from_clipboard(clipboard).where(name: parent_element.definition["nestable_elements"])
180
191
  end
181
-
182
- deprecate available: :published, deprecator: Alchemy::Deprecation
183
192
  end
184
193
 
185
194
  # Returns next public element from same page.
@@ -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"
@@ -152,6 +153,7 @@ module Alchemy
152
153
  after_update :touch_nodes
153
154
 
154
155
  # Concerns
156
+ include PageLayouts
155
157
  include PageScopes
156
158
  include PageNatures
157
159
  include PageNaming
@@ -267,11 +269,11 @@ module Alchemy
267
269
  where(id: clipboard.collect { |p| p["id"] })
268
270
  end
269
271
 
270
- def all_from_clipboard_for_select(clipboard, language_id, layoutpage = false)
272
+ def all_from_clipboard_for_select(clipboard, language_id, layoutpages: false)
271
273
  return [] if clipboard.blank?
272
274
 
273
275
  clipboard_pages = all_from_clipboard(clipboard)
274
- allowed_page_layouts = Alchemy::PageLayout.selectable_layouts(language_id, layoutpage)
276
+ allowed_page_layouts = Alchemy::Page.selectable_layouts(language_id, layoutpages: layoutpages)
275
277
  allowed_page_layout_names = allowed_page_layouts.collect { |p| p["name"] }
276
278
  clipboard_pages.select { |cp| allowed_page_layout_names.include?(cp.page_layout) }
277
279
  end
@@ -30,7 +30,7 @@ module Alchemy
30
30
  # store the processed image
31
31
  image.to_file(server_path(uid)).close
32
32
  rescue RuntimeError => e
33
- Rails.logger.warn(e)
33
+ ErrorTracking.notification_handler.call(e)
34
34
  # destroy the thumb if processing or storing fails
35
35
  thumb&.destroy
36
36
  end
@@ -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 %>
@@ -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,
@@ -34,6 +34,7 @@
34
34
  order_admin_elements_path: '<%= alchemy.order_admin_elements_path %>',
35
35
  link_admin_pages_path: '<%= alchemy.link_admin_pages_path %>',
36
36
  api_pages_path: '<%= alchemy.api_pages_path %>',
37
- api_elements_path: '<%= alchemy.api_elements_path %>'
37
+ api_elements_path: '<%= alchemy.api_elements_path %>',
38
+ api_ingredients_path: '<%= alchemy.api_ingredients_path %>'
38
39
  };
39
40
  </script>
@@ -1,3 +1,6 @@
1
+ <% has_level_select = headline_editor.level_options.many? %>
2
+ <% has_size_select = headline_editor.size_options.many? %>
3
+
1
4
  <%= content_tag :div,
2
5
  class: headline_editor.css_classes,
3
6
  data: headline_editor.data_attributes do %>
@@ -5,26 +8,25 @@
5
8
  <%= ingredient_label(headline_editor) %>
6
9
  <%= f.text_field :value, id: nil %>
7
10
 
8
- <div class="input-row">
9
- <% if headline_editor.level_options.length > 1 %>
10
- <div class="input-column">
11
- <%= f.label :level %>
12
- <%= f.select :level,
13
- options_for_select(headline_editor.level_options, headline_editor.level),
14
- {},
15
- { class: "alchemy_selectbox full_width" } %>
16
- </div>
17
- <% end %>
11
+ <% if headline_editor.settings[:anchor] %>
12
+ <%= render "alchemy/ingredients/shared/anchor", ingredient_editor: headline_editor %>
13
+ <% end %>
14
+
15
+ <% if has_level_select %>
16
+ <div class="input-addon right<%= " second" if has_size_select %>">
17
+ <%= f.select :level,
18
+ options_for_select(headline_editor.level_options, headline_editor.level),
19
+ {},
20
+ { class: "custom-select", title: f.object.class.human_attribute_name(:level) } %>
21
+ </div>
22
+ <% end %>
18
23
 
19
- <% if headline_editor.size_options.length > 1 %>
20
- <div class="input-column">
21
- <%= f.label :size %>
22
- <%= f.select :size,
23
- options_for_select(headline_editor.size_options, headline_editor.size),
24
- {},
25
- { class: "alchemy_selectbox full_width" } %>
26
- </div>
27
- <% end %>
28
- </div>
24
+ <% if has_size_select %>
25
+ <div class="input-addon right">
26
+ <%= f.select :size, options_for_select(headline_editor.size_options, headline_editor.size),
27
+ {},
28
+ { class: "custom-select", title: f.object.class.human_attribute_name(:size) } %>
29
+ </div>
30
+ <% end %>
29
31
  <% end %>
30
32
  <% end %>
@@ -2,6 +2,7 @@
2
2
 
3
3
  <%= content_tag "h#{headline_view.level}",
4
4
  headline_view.value,
5
+ id: headline_view.dom_id.presence,
5
6
  class: [
6
7
  headline_view.size ? "h#{headline_view.size}" : nil,
7
8
  html_options[:class]
@@ -9,6 +9,9 @@
9
9
  class: text_editor.settings[:linkable] ? "text_with_icon" : "",
10
10
  id: nil,
11
11
  type: text_editor.settings[:input_type] || "text" %>
12
+ <% if text_editor.settings[:anchor] %>
13
+ <%= render "alchemy/ingredients/shared/anchor", ingredient_editor: text_editor %>
14
+ <% end %>
12
15
  <% if text_editor.settings[:linkable] %>
13
16
  <%= f.hidden_field :link, "data-link-value": true, id: nil %>
14
17
  <%= f.hidden_field :link_title, "data-link-title": true, id: nil %>
@@ -1,13 +1,17 @@
1
1
  <%- options = local_assigns.fetch(:options, {}) -%>
2
2
  <%- html_options = local_assigns.fetch(:html_options, {}) -%>
3
- <%- if text_view.link.blank? ||
4
- text_view.settings_value(:disable_link, options) -%>
5
- <%= text_view.value -%>
3
+ <%- if text_view.link.blank? || text_view.settings_value(:disable_link, options) -%>
4
+ <%- if text_view.dom_id.present? -%>
5
+ <%= content_tag :a, text_view.value, id: text_view.dom_id %>
6
+ <% else %>
7
+ <%= text_view.value -%>
8
+ <%- end -%>
6
9
  <%- else -%>
7
10
  <%= link_to(
8
11
  text_view.value,
9
12
  url_for(text_view.link),
10
13
  {
14
+ id: text_view.dom_id.presence,
11
15
  title: text_view.link_title,
12
16
  target: (text_view.link_target == "blank" ? "_blank" : nil),
13
17
  'data-link-target' => text_view.link_target
@@ -0,0 +1,9 @@
1
+ <span class="edit-ingredient-anchor-link">
2
+ <%= link_to_dialog render_icon(:bookmark, { style: ingredient_editor.dom_id.present? ? "solid" : "regular" }),
3
+ alchemy.edit_admin_ingredient_path(id: ingredient_editor.id),
4
+ {
5
+ title: Alchemy.t(:edit_anchor),
6
+ size: "380x125"
7
+ },
8
+ title: Alchemy.t(:edit_anchor) %>
9
+ </span>
@@ -1,3 +1,5 @@
1
+ <!-- Diese Vorlage stimmt mit den config Mailer-Feldern überein. Wenn Sie die Konfiguration ändern, erstellen Sie bitte eine eigene Datei -->
2
+
1
3
  <%= @message.message %>
2
4
 
3
5
  --
@@ -1,3 +1,5 @@
1
+ <!-- This template matches the config mailer fields. If you change the config, please create your own file: app/views/alchemy/messages_mailer/contact_form_mail.text.erb -->
2
+
1
3
  <%= @message.message %>
2
4
 
3
5
  --
@@ -1,3 +1,5 @@
1
+ <!-- Esta plantilla coincide con los campos de correo config. Si cambia la configuración, cree su propio archivo -->
2
+
1
3
  <%= @message.message %>
2
4
 
3
5
  --
@@ -228,7 +228,8 @@ en:
228
228
 
229
229
  add_nested_element: "Add %{name}"
230
230
  anchor: 'Anchor'
231
- anchor_link_headline: "You can link to an element anchor from the actual page."
231
+ anchor_link_headline: You can link to an anchor from the current page.
232
+ automatic_anchor_notice: The anchor is generated automatically.
232
233
  attribute_fixed: Value can't be changed for this page type
233
234
  back: 'back'
234
235
  locked_pages: "Active pages"
@@ -467,7 +468,7 @@ en:
467
468
  image_name: "Name: %{name}"
468
469
  image_title: "Title-tag"
469
470
  internal_link_headline: "Search for a page to link to by entering its name into the Page select."
470
- internal_link_page_elements_explanation: "Additionally you can choose an anchor to an element from selected page."
471
+ internal_link_page_elements_explanation: "Additionally you can choose an anchor to link to from selected page."
471
472
  "item copied to clipboard": "Copied %{name} to clipboard"
472
473
  "item moved to clipboard": "Moved %{name} to clipboard"
473
474
  "item removed from clipboard": "Removed %{name} from clipboard"
@@ -845,6 +846,8 @@ en:
845
846
  crop_from: Crop from
846
847
  crop_size: Crop size
847
848
  picture_id: Bild
849
+ alchemy/ingredient:
850
+ dom_id: Anchor
848
851
  alchemy/language:
849
852
  country_code: "Country code"
850
853
  language_code: "Language code"
data/config/routes.rb CHANGED
@@ -126,6 +126,7 @@ Alchemy::Engine.routes.draw do
126
126
 
127
127
  namespace :api, defaults: { format: "json" } do
128
128
  resources :contents, only: [:index, :show]
129
+ resources :ingredients, only: [:index]
129
130
 
130
131
  resources :elements, only: [:index, :show] do
131
132
  get "/contents" => "contents#index", as: "contents"
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module ErrorTracking
5
+ class ErrorLogger < BaseHandler
6
+ def self.call(exception)
7
+ ::Rails.logger.tagged("alchemy_cms") do
8
+ ::Rails.logger.error("#{exception.class.name}: #{exception.message} in #{exception.backtrace.first}")
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -9,6 +9,8 @@ module Alchemy
9
9
  end
10
10
 
11
11
  mattr_accessor :notification_handler
12
- @@notification_handler = BaseHandler
13
12
  end
14
13
  end
14
+
15
+ require "alchemy/error_tracking/error_logger"
16
+ Alchemy::ErrorTracking.notification_handler = Alchemy::ErrorTracking::ErrorLogger
@@ -31,13 +31,7 @@ module Alchemy
31
31
  end
32
32
 
33
33
  def inject_seeder
34
- seed_file = Rails.root.join("db", "seeds.rb")
35
- args = [seed_file, "Alchemy::Seeder.seed!\n"]
36
- if File.exist?(seed_file)
37
- append_file(*args)
38
- else
39
- add_file(*args)
40
- end
34
+ append_file "./db/seeds.rb", "Alchemy::Seeder.seed!\n"
41
35
  end
42
36
  end
43
37
  end