alchemy_cms 6.0.14 → 6.1.0

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