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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +0 -3
- data/.github/workflows/stale.yml +2 -0
- data/CHANGELOG.md +34 -5
- data/Gemfile +2 -2
- data/alchemy_cms.gemspec +2 -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/controllers/alchemy/pages_controller.rb +19 -5
- data/app/decorators/alchemy/ingredient_editor.rb +4 -0
- data/app/helpers/alchemy/elements_helper.rb +0 -8
- 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/presenters.rb +1 -1
- data/app/models/alchemy/element.rb +12 -3
- 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 +4 -2
- data/app/models/alchemy/picture_thumb/create.rb +1 -1
- 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/_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 +5 -2
- data/config/routes.rb +1 -0
- data/lib/alchemy/error_tracking/error_logger.rb +13 -0
- data/lib/alchemy/error_tracking.rb +3 -1
- data/lib/alchemy/install/tasks.rb +1 -7
- 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/add_page_versions.rb +2 -2
- data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +7 -5
- data/lib/alchemy/version.rb +1 -1
- data/lib/alchemy.rb +4 -0
- data/lib/alchemy_cms.rb +1 -1
- data/lib/generators/alchemy/elements/templates/view.html.erb +1 -1
- data/lib/generators/alchemy/elements/templates/view.html.haml +1 -1
- data/lib/generators/alchemy/elements/templates/view.html.slim +1 -1
- data/lib/tasks/alchemy/thumbnails.rake +1 -1
- 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 +32 -6
- data/lib/non_stupid_digest_assets.rb +0 -55
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
|
@@ -15,10 +15,10 @@ module Alchemy::Upgrader::Tasks
|
|
15
15
|
Alchemy::Page.transaction do
|
16
16
|
page.versions.create!(
|
17
17
|
public_on: page.legacy_public_on,
|
18
|
-
public_until: page.legacy_public_until
|
18
|
+
public_until: page.legacy_public_until,
|
19
19
|
).tap do |version|
|
20
20
|
# We must not use .find_each here to not mess up the order of elements
|
21
|
-
page.draft_version.elements.not_nested.
|
21
|
+
page.draft_version.elements.not_nested.published.each do |element|
|
22
22
|
Alchemy::Element.copy(element, page_version_id: version.id)
|
23
23
|
end
|
24
24
|
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
|
data/lib/alchemy_cms.rb
CHANGED
@@ -46,7 +46,7 @@ namespace :alchemy do
|
|
46
46
|
ingredient_pictures = Alchemy::Ingredients::Picture.
|
47
47
|
joins(:element).
|
48
48
|
preload({ related_object: :thumbs }).
|
49
|
-
merge(Alchemy::Element.
|
49
|
+
merge(Alchemy::Element.published)
|
50
50
|
|
51
51
|
if ENV["ELEMENTS"].present?
|
52
52
|
ingredient_pictures = ingredient_pictures.merge(
|
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
|
3
|
+
"version": "6.1.0",
|
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"
|