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
@@ -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"
@@ -593,6 +594,7 @@ en:
593
594
  saved_link: "Link saved."
594
595
  search: "search"
595
596
  search_engines: "Search engines"
597
+ fulltext_search: "Fulltext search"
596
598
  select_element: "Select element"
597
599
  seperate_tags_with_comma: "Seperate tags with comma"
598
600
  show_element_content: "Show content of this element."
@@ -845,6 +847,8 @@ en:
845
847
  crop_from: Crop from
846
848
  crop_size: Crop size
847
849
  picture_id: Bild
850
+ alchemy/ingredient:
851
+ dom_id: Anchor
848
852
  alchemy/language:
849
853
  country_code: "Country code"
850
854
  language_code: "Language code"
@@ -876,6 +880,7 @@ en:
876
880
  page_layout: "Page type"
877
881
  public: "public"
878
882
  restricted: "restricted"
883
+ searchable: "show in search"
879
884
  robot_follow: "robot may follow links"
880
885
  robot_index: "allow robot to index"
881
886
  sitemap: "visible in sitemap"
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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddSearchableToAlchemyPages < ActiveRecord::Migration[6.0]
4
+ def change
5
+ return if column_exists?(:alchemy_pages, :searchable)
6
+
7
+ add_column :alchemy_pages, :searchable, :boolean, default: true, null: false
8
+ end
9
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Alchemy
3
- Deprecation = ActiveSupport::Deprecation.new("6.1", "Alchemy")
3
+ Deprecation = ActiveSupport::Deprecation.new("7.0", "Alchemy")
4
4
  end
@@ -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
@@ -41,112 +41,8 @@ module Alchemy
41
41
  all.detect { |a| a["name"].casecmp(name).zero? }
42
42
  end
43
43
 
44
- def get_all_by_attributes(attributes)
45
- return [] if attributes.blank?
46
-
47
- if attributes.is_a? Hash
48
- layouts = []
49
- attributes.stringify_keys.each do |key, value|
50
- result = all.select { |l| l.key?(key) && l[key].to_s.casecmp(value.to_s).zero? }
51
- layouts += result unless result.empty?
52
- end
53
- layouts
54
- else
55
- []
56
- end
57
- end
58
-
59
- # Returns page layouts ready for Rails' select form helper.
60
- #
61
- def layouts_for_select(language_id, only_layoutpages = false)
62
- @map_array = []
63
- mapped_layouts_for_select(selectable_layouts(language_id, only_layoutpages))
64
- end
65
-
66
- # Returns all layouts that can be used for creating a new page.
67
- #
68
- # It removes all layouts from available layouts that are unique and already taken and that are marked as hide.
69
- #
70
- # @param [Fixnum]
71
- # language_id of current used Language.
72
- # @param [Boolean] (false)
73
- # Pass true to only select layouts for global/layout pages.
74
- #
75
- def selectable_layouts(language_id, only_layoutpages = false)
76
- @language_id = language_id
77
- all.select do |layout|
78
- if only_layoutpages
79
- layout["layoutpage"] && layout_available?(layout)
80
- else
81
- !layout["layoutpage"] && layout_available?(layout)
82
- end
83
- end
84
- end
85
-
86
- # Returns all names of elements defined in given page layout.
87
- #
88
- def element_names_for(page_layout)
89
- if definition = get(page_layout)
90
- definition.fetch("elements", [])
91
- else
92
- Rails.logger.warn "\n+++ Warning: No layout definition for #{page_layout} found! in page_layouts.yml\n"
93
- []
94
- end
95
- end
96
-
97
- # Translates name for given layout
98
- #
99
- # === Translation example
100
- #
101
- # en:
102
- # alchemy:
103
- # page_layout_names:
104
- # products_overview: Products Overview
105
- #
106
- # @param [String]
107
- # The layout name
108
- #
109
- def human_layout_name(layout)
110
- Alchemy.t(layout, scope: "page_layout_names", default: layout.to_s.humanize)
111
- end
112
-
113
44
  private
114
45
 
115
- # Returns true if the given layout is unique and not already taken or it should be hidden.
116
- #
117
- def layout_available?(layout)
118
- !layout["hide"] && !already_taken?(layout) && available_on_site?(layout)
119
- end
120
-
121
- # Returns true if this layout is unique and already taken by another page.
122
- #
123
- def already_taken?(layout)
124
- layout["unique"] && page_with_layout_existing?(layout["name"])
125
- end
126
-
127
- # Returns true if one page already has the given layout
128
- #
129
- def page_with_layout_existing?(layout)
130
- Alchemy::Page.where(page_layout: layout, language_id: @language_id).pluck(:id).any?
131
- end
132
-
133
- # Returns true if given layout is available for current site.
134
- #
135
- # If no site layouts are defined it always returns true.
136
- #
137
- # == Example
138
- #
139
- # # config/alchemy/site_layouts.yml
140
- # - name: default_site
141
- # page_layouts: [default_intro]
142
- #
143
- def available_on_site?(layout)
144
- return false unless Alchemy::Site.current
145
-
146
- Alchemy::Site.current.definition.blank? ||
147
- Alchemy::Site.current.definition.fetch("page_layouts", []).include?(layout["name"])
148
- end
149
-
150
46
  # Reads the layout definitions from +config/alchemy/page_layouts.yml+.
151
47
  #
152
48
  def read_definitions_file
@@ -168,15 +64,6 @@ module Alchemy
168
64
  def layouts_file_path
169
65
  Rails.root.join "config/alchemy/page_layouts.yml"
170
66
  end
171
-
172
- # Maps given layouts for Rails select form helper.
173
- #
174
- def mapped_layouts_for_select(layouts)
175
- layouts.each do |layout|
176
- @map_array << [human_layout_name(layout["name"]), layout["name"]]
177
- end
178
- @map_array
179
- end
180
67
  end
181
68
  end
182
69
  end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples_for "having dom ids" do
4
+ let(:element) { build(:alchemy_element, name: "element_with_ingredients") }
5
+
6
+ let(:ingredient) do
7
+ described_class.new(
8
+ element: element,
9
+ role: "headline",
10
+ )
11
+ end
12
+
13
+ describe "setting dom id from value" do
14
+ subject do
15
+ ingredient.valid? && ingredient.dom_id
16
+ end
17
+
18
+ before do
19
+ expect_any_instance_of(described_class).to receive(:settings).at_least(:once) { settings }
20
+ end
21
+
22
+ context "without anchor settings" do
23
+ let(:settings) do
24
+ {}
25
+ end
26
+
27
+ it "does not set a dom_id" do
28
+ is_expected.to be_nil
29
+ end
30
+ end
31
+
32
+ context "with anchor setting set to true" do
33
+ let(:settings) do
34
+ { anchor: true }
35
+ end
36
+
37
+ it "parameterizes dom_id" do
38
+ ingredient.dom_id = "SE Headline"
39
+ is_expected.to eq "se-headline"
40
+ end
41
+ end
42
+
43
+ context "with anchor setting set to from_value" do
44
+ let(:settings) do
45
+ { anchor: "from_value" }
46
+ end
47
+
48
+ context "with a value present" do
49
+ let(:ingredient) do
50
+ described_class.new(
51
+ element: element,
52
+ role: "headline",
53
+ value: "Hello World",
54
+ )
55
+ end
56
+
57
+ it "sets a dom_id from value" do
58
+ is_expected.to eq "hello-world"
59
+ end
60
+ end
61
+
62
+ context "with no value present" do
63
+ let(:ingredient) do
64
+ described_class.new(
65
+ element: element,
66
+ role: "headline",
67
+ value: "",
68
+ )
69
+ end
70
+
71
+ it "sets no dom_id" do
72
+ is_expected.to eq ""
73
+ end
74
+ end
75
+ end
76
+
77
+ context "with anchor setting set to fixed value" do
78
+ context "that is false" do
79
+ let(:settings) do
80
+ { anchor: false }
81
+ end
82
+
83
+ it "sets no dom_id" do
84
+ is_expected.to be_nil
85
+ end
86
+ end
87
+
88
+ context "that is true" do
89
+ let(:settings) do
90
+ { anchor: true }
91
+ end
92
+
93
+ it "sets no dom_id" do
94
+ is_expected.to be_nil
95
+ end
96
+ end
97
+
98
+ context "that is from_value" do
99
+ let(:settings) do
100
+ { anchor: true }
101
+ end
102
+
103
+ it "sets no dom_id" do
104
+ is_expected.to be_nil
105
+ end
106
+ end
107
+
108
+ context "that is a non reserved value" do
109
+ let(:settings) do
110
+ { anchor: "FixED VALUE" }
111
+ end
112
+
113
+ it "sets the dom_id to fixed value" do
114
+ is_expected.to eq "fixed-value"
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -7,7 +7,7 @@ module Alchemy::Upgrader::Tasks
7
7
  include Thor::Actions
8
8
 
9
9
  no_tasks do
10
- def create_ingredients
10
+ def create_ingredients(verbose: !Rails.env.test?)
11
11
  Alchemy::Deprecation.silence do
12
12
  elements_with_ingredients = Alchemy::ElementDefinition.all.select { |d| d.key?(:ingredients) }
13
13
  if ENV["ONLY"]
@@ -22,13 +22,13 @@ module Alchemy::Upgrader::Tasks
22
22
  elements_with_ingredients.map do |element_definition|
23
23
  elements = all_elements.select { |e| e.name == element_definition[:name] }
24
24
  if elements.any?
25
- puts "-- Creating ingredients for #{elements.count} #{element_definition[:name]}(s)"
25
+ puts "-- Creating ingredients for #{elements.count} #{element_definition[:name]}(s)" if verbose
26
26
  elements.each do |element|
27
27
  MigrateElementIngredients.call(element)
28
- print "."
28
+ print "." if verbose
29
29
  end
30
- puts "\n"
31
- else
30
+ puts "\n" if verbose
31
+ elsif verbose
32
32
  puts "-- No #{element_definition[:name]} elements found for migration."
33
33
  end
34
34
  end
@@ -56,6 +56,8 @@ module Alchemy::Upgrader::Tasks
56
56
  ingredient.value = content.ingredient
57
57
  end
58
58
  data = ingredient.class.stored_attributes.fetch(:data, []).each_with_object({}) do |attr, d|
59
+ next unless essence.respond_to?(attr)
60
+
59
61
  d[attr] = essence.public_send(attr)
60
62
  end
61
63
  ingredient.data = data
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- VERSION = "6.0.12"
4
+ VERSION = "6.1.1"
5
5
 
6
6
  def self.version
7
7
  VERSION
data/lib/alchemy.rb CHANGED
@@ -57,6 +57,10 @@ module Alchemy
57
57
  @_preview_sources ||= Set.new << Alchemy::Admin::PreviewUrl
58
58
  end
59
59
 
60
+ def self.preview_sources=(sources)
61
+ @_preview_sources = Array(sources)
62
+ end
63
+
60
64
  # Define page publish targets
61
65
  #
62
66
  # A publish target is a ActiveJob that gets performed
@@ -80,4 +84,17 @@ module Alchemy
80
84
  def self.publish_targets
81
85
  @_publish_targets ||= Set.new
82
86
  end
87
+
88
+ # Enable full text search configuration
89
+ #
90
+ # It enables a searchable checkbox in the page form to toggle
91
+ # the searchable field. These information can used in a search
92
+ # plugin (e.g. https://github.com/AlchemyCMS/alchemy-pg_search).
93
+ #
94
+ # == Example
95
+ #
96
+ # # config/initializers/alchemy.rb
97
+ # Alchemy.enable_searchable = true
98
+ #
99
+ mattr_accessor :enable_searchable, default: false
83
100
  end
data/package/admin.js CHANGED
@@ -2,6 +2,7 @@ import translate from "./src/i18n"
2
2
  import translationData from "./src/translations"
3
3
  import NodeTree from "./src/node_tree"
4
4
  import fileEditors from "./src/file_editors"
5
+ import IngredientAnchorLink from "./src/ingredient_anchor_link"
5
6
  import pictureEditors from "./src/picture_editors"
6
7
  import ImageLoader from "./src/image_loader"
7
8
  import ImageCropper from "./src/image_cropper"
@@ -24,6 +25,7 @@ Object.assign(Alchemy, {
24
25
  pictureEditors,
25
26
  ImageLoader: ImageLoader.init,
26
27
  ImageCropper,
28
+ IngredientAnchorLink,
27
29
  Datepicker,
28
30
  Sitemap,
29
31
  PagePublicationFields
@@ -0,0 +1,17 @@
1
+ export default class IngredientAnchorLink {
2
+ static updateIcon(ingredientId, active = false) {
3
+ const ingredientEditor = document.querySelector(
4
+ `[data-ingredient-id="${ingredientId}"]`
5
+ )
6
+ if (ingredientEditor) {
7
+ const icon = ingredientEditor.querySelector(
8
+ ".edit-ingredient-anchor-link > a > .icon"
9
+ )
10
+ if (icon) {
11
+ active
12
+ ? icon.classList.replace("far", "fas")
13
+ : icon.classList.replace("fas", "far")
14
+ }
15
+ }
16
+ }
17
+ }
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alchemy_cms/admin",
3
- "version": "6.0.12",
3
+ "version": "6.1.1",
4
4
  "description": "AlchemyCMS",
5
5
  "browser": "package/admin.js",
6
6
  "files": [
@@ -31,7 +31,7 @@
31
31
  "devDependencies": {
32
32
  "@babel/core": "^7.9.6",
33
33
  "@babel/preset-env": "^7.9.6",
34
- "babel-jest": "^27.0.1",
34
+ "babel-jest": "^29.0.1",
35
35
  "jest": "^25.2.7",
36
36
  "prettier": "^2.0.2",
37
37
  "xhr-mock": "^2.5.1"