alchemy_cms 6.0.12 → 6.1.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/brakeman-analysis.yml +26 -26
- data/.github/workflows/ci.yml +1 -1
- data/.github/workflows/stale.yml +2 -0
- data/CHANGELOG.md +45 -0
- data/Gemfile +2 -2
- data/alchemy_cms.gemspec +1 -1
- data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +13 -11
- data/app/assets/stylesheets/alchemy/_variables.scss +9 -4
- data/app/assets/stylesheets/alchemy/elements.scss +96 -4
- data/app/assets/stylesheets/alchemy/forms.scss +59 -17
- data/app/assets/stylesheets/tinymce/skins/alchemy/content.min.css.scss +3 -3
- data/app/controllers/alchemy/admin/ingredients_controller.rb +1 -1
- data/app/controllers/alchemy/admin/pages_controller.rb +3 -3
- data/app/controllers/alchemy/api/ingredients_controller.rb +22 -0
- data/app/controllers/alchemy/messages_controller.rb +1 -1
- data/app/decorators/alchemy/ingredient_editor.rb +4 -0
- data/app/helpers/alchemy/url_helper.rb +0 -8
- data/app/models/alchemy/element/definitions.rb +16 -2
- data/app/models/alchemy/element/dom_id.rb +30 -0
- data/app/models/alchemy/element/element_contents.rb +39 -0
- data/app/models/alchemy/element/element_essences.rb +9 -1
- data/app/models/alchemy/element/element_ingredients.rb +7 -0
- data/app/models/alchemy/element/presenters.rb +1 -1
- data/app/models/alchemy/element.rb +14 -0
- data/app/models/alchemy/ingredients/headline.rb +4 -1
- data/app/models/alchemy/ingredients/text.rb +3 -0
- data/app/models/alchemy/page/page_layouts.rb +128 -0
- data/app/models/alchemy/page.rb +5 -2
- data/app/models/alchemy/picture_thumb/create.rb +15 -3
- data/app/models/concerns/alchemy/dom_ids.rb +32 -0
- data/app/serializers/alchemy/ingredient_serializer.rb +11 -0
- data/app/views/alchemy/admin/elements/update.js.erb +3 -0
- data/app/views/alchemy/admin/ingredients/_dom_id_fields.html.erb +4 -0
- data/app/views/alchemy/admin/ingredients/_headline_fields.html.erb +3 -0
- data/app/views/alchemy/admin/ingredients/_text_fields.html.erb +3 -0
- data/app/views/alchemy/admin/ingredients/update.js.erb +7 -0
- data/app/views/alchemy/admin/languages/_form.html.erb +1 -1
- data/app/views/alchemy/admin/languages/_language.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_form.html.erb +9 -0
- data/app/views/alchemy/admin/pages/_page_layout_filter.html.erb +1 -1
- data/app/views/alchemy/admin/partials/_routes.html.erb +2 -1
- data/app/views/alchemy/ingredients/_headline_editor.html.erb +22 -20
- data/app/views/alchemy/ingredients/_headline_view.html.erb +1 -0
- data/app/views/alchemy/ingredients/_text_editor.html.erb +3 -0
- data/app/views/alchemy/ingredients/_text_view.html.erb +7 -3
- data/app/views/alchemy/ingredients/shared/_anchor.html.erb +9 -0
- data/app/views/alchemy/messages_mailer/contact_form_mail.de.text.erb +2 -0
- data/app/views/alchemy/messages_mailer/contact_form_mail.en.text.erb +2 -0
- data/app/views/alchemy/messages_mailer/contact_form_mail.es.text.erb +2 -0
- data/config/locales/alchemy.en.yml +7 -2
- data/config/routes.rb +1 -0
- data/db/migrate/20230123112425_add_searchable_to_alchemy_pages.rb +9 -0
- data/lib/alchemy/deprecation.rb +1 -1
- data/lib/alchemy/error_tracking/error_logger.rb +13 -0
- data/lib/alchemy/error_tracking.rb +3 -1
- data/lib/alchemy/page_layout.rb +0 -113
- data/lib/alchemy/test_support/shared_dom_ids_examples.rb +119 -0
- data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +7 -5
- data/lib/alchemy/version.rb +1 -1
- data/lib/alchemy.rb +17 -0
- data/package/admin.js +2 -0
- data/package/src/ingredient_anchor_link.js +17 -0
- data/package.json +2 -2
- data/vendor/assets/javascripts/tinymce/tinymce.min.js +2 -2
- metadata +19 -5
@@ -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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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 %>
|
@@ -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
|
-
|
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>
|
@@ -228,7 +228,8 @@ en:
|
|
228
228
|
|
229
229
|
add_nested_element: "Add %{name}"
|
230
230
|
anchor: 'Anchor'
|
231
|
-
anchor_link_headline:
|
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
|
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"
|
data/lib/alchemy/deprecation.rb
CHANGED
@@ -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
|
data/lib/alchemy/page_layout.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/alchemy/version.rb
CHANGED
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.
|
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": "^
|
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"
|