alchemy_cms 6.0.5 → 6.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +26 -0
- data/app/assets/stylesheets/alchemy/elements.scss +35 -0
- data/app/controllers/alchemy/admin/pages_controller.rb +5 -1
- data/app/controllers/alchemy/pages_controller.rb +19 -5
- data/app/decorators/alchemy/element_editor.rb +23 -0
- data/app/jobs/alchemy/publish_page_job.rb +4 -1
- data/app/models/alchemy/eager_loading.rb +39 -0
- data/app/models/alchemy/page/publisher.rb +4 -1
- data/app/models/alchemy/page.rb +2 -7
- data/app/models/alchemy/page_version.rb +1 -1
- data/app/views/alchemy/admin/elements/_element.html.erb +34 -5
- data/lib/alchemy/errors.rb +8 -2
- data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +1 -1
- data/lib/alchemy/version.rb +1 -1
- data/lib/tasks/alchemy/thumbnails.rake +24 -0
- data/package.json +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ac4238ff0e22f613444f1ae9124a20bf4e884f2f7c4f8fe05c1e8d0bd147cad7
|
4
|
+
data.tar.gz: 02e2877398db81eb4c58a1afbad4c0355020da0fb7505c895d493d40a93223d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2bacfe8d1e126cc3a3e31f5dfdb1346efb1398adf33dac32e4e58731bcfd11580d51544797a59d563361ee28c5d4a37a76a7659b7534651a574c7903820cc994
|
7
|
+
data.tar.gz: '04198e8f916fd87a5a1587129b86480cde12cbab889acfa2baaf23a34ed966292cc24312c1b731648c556e27fc3045b0a5b56d43ab6124abacbcb33cdf05fab2'
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## 6.0.6 (2022-05-30)
|
2
|
+
|
3
|
+
- Add task to eagerly generate ingredients pictures thumbnails [#2342](https://github.com/AlchemyCMS/alchemy_cms/pull/2342) ([tvdeyen](https://github.com/tvdeyen))
|
4
|
+
- Add spec for ingredients migrator; fix preloading in Rails 6.1+ [#2340](https://github.com/AlchemyCMS/alchemy_cms/pull/2340) ([mamhoff](https://github.com/mamhoff))
|
5
|
+
- Extract element ingredient migrator [#2337](https://github.com/AlchemyCMS/alchemy_cms/pull/2337) ([tvdeyen](https://github.com/tvdeyen))
|
6
|
+
- Update published_at only after page has been published. [#2331](https://github.com/AlchemyCMS/alchemy_cms/pull/2331) ([pascalbetz](https://github.com/pascalbetz))
|
7
|
+
- Add element groups functionality for cleaning up editors [#2124](https://github.com/AlchemyCMS/alchemy_cms/pull/2124) ([dbwinger](https://github.com/dbwinger))
|
8
|
+
- Eagerload at the controller or job layer [#2313](https://github.com/AlchemyCMS/alchemy_cms/pull/2313) ([mamhoff](https://github.com/mamhoff))
|
9
|
+
|
1
10
|
## 6.0.5 (2022-05-11)
|
2
11
|
|
3
12
|
- Extract element ingredient migrator [#2337](https://github.com/AlchemyCMS/alchemy_cms/pull/2337) ([tvdeyen](https://github.com/tvdeyen))
|
@@ -15,6 +15,7 @@ Alchemy.ElementEditors =
|
|
15
15
|
init: ->
|
16
16
|
@element_area = $("#element_area")
|
17
17
|
@bindEvents()
|
18
|
+
@expandContentGroups()
|
18
19
|
return
|
19
20
|
|
20
21
|
# Binds click events on several DOM elements from element editors
|
@@ -36,12 +37,20 @@ Alchemy.ElementEditors =
|
|
36
37
|
# Binds the custom SaveElement event
|
37
38
|
@element_area.on "SaveElement.Alchemy", '.element-editor', (e, data) =>
|
38
39
|
@onSaveElement(e, data)
|
40
|
+
@element_area.on "click", '[data-toggle-content-group]', (e) =>
|
41
|
+
@onToggleContentGroup(e)
|
39
42
|
# Listen to postMessage messages from the preview frame
|
40
43
|
window.addEventListener 'message', (e) =>
|
41
44
|
@onMessage(e.data)
|
42
45
|
true
|
43
46
|
return
|
44
47
|
|
48
|
+
# Expands content groups that are stored in sessionStorage as expanded
|
49
|
+
expandContentGroups: ->
|
50
|
+
if $expanded_content_groups = sessionStorage.getItem('Alchemy.expanded_content_groups')
|
51
|
+
for header_id in JSON.parse($expanded_content_groups) then do (header_id) =>
|
52
|
+
$('#' + header_id).closest('.content-group').addClass('expanded');
|
53
|
+
|
45
54
|
# Selects and scrolls to element with given id in the preview window.
|
46
55
|
#
|
47
56
|
focusElementPreview: (element_id) ->
|
@@ -170,6 +179,23 @@ Alchemy.ElementEditors =
|
|
170
179
|
Alchemy.Buttons.enable($element)
|
171
180
|
true
|
172
181
|
|
182
|
+
# Toggle visibility of the content fields in the group
|
183
|
+
onToggleContentGroup: (event) ->
|
184
|
+
$group_div = $(event.currentTarget).closest('.content-group');
|
185
|
+
$group_div.toggleClass('expanded');
|
186
|
+
|
187
|
+
$expanded_content_groups = JSON.parse(sessionStorage.getItem('Alchemy.expanded_content_groups') || '[]');
|
188
|
+
# Add or remove depending on whether this content group is expanded
|
189
|
+
if $group_div.hasClass('expanded')
|
190
|
+
if $expanded_content_groups.indexOf(event.currentTarget.id) == -1
|
191
|
+
$expanded_content_groups.push(event.currentTarget.id);
|
192
|
+
else
|
193
|
+
$expanded_content_groups = $expanded_content_groups.filter (value) ->
|
194
|
+
value != event.currentTarget.id
|
195
|
+
|
196
|
+
sessionStorage.setItem('Alchemy.expanded_content_groups', JSON.stringify($expanded_content_groups))
|
197
|
+
false
|
198
|
+
|
173
199
|
# Event handlers
|
174
200
|
|
175
201
|
onMessage: (data) ->
|
@@ -414,6 +414,41 @@
|
|
414
414
|
}
|
415
415
|
}
|
416
416
|
|
417
|
+
.content-group {
|
418
|
+
width: 100%;
|
419
|
+
padding: $default-padding 0;
|
420
|
+
position: relative;
|
421
|
+
border-bottom: 1px solid $medium-gray;
|
422
|
+
|
423
|
+
&:last-child {
|
424
|
+
border-bottom: none;
|
425
|
+
padding-bottom: 0;
|
426
|
+
}
|
427
|
+
|
428
|
+
.content-group-header {
|
429
|
+
display: flex;
|
430
|
+
align-items: center;
|
431
|
+
justify-content: space-between;
|
432
|
+
font-weight: bold;
|
433
|
+
text-decoration: none;
|
434
|
+
padding: $default-padding 1px;
|
435
|
+
}
|
436
|
+
|
437
|
+
.content-group-contents {
|
438
|
+
display: none;
|
439
|
+
}
|
440
|
+
|
441
|
+
&.expanded {
|
442
|
+
.content-group-contents {
|
443
|
+
display: block;
|
444
|
+
}
|
445
|
+
|
446
|
+
.content-group-expand {
|
447
|
+
@extend .fa-angle-up;
|
448
|
+
}
|
449
|
+
}
|
450
|
+
}
|
451
|
+
|
417
452
|
.element-content-editors,
|
418
453
|
.element-ingredient-editors {
|
419
454
|
display: flex;
|
@@ -324,7 +324,7 @@ module Alchemy
|
|
324
324
|
end
|
325
325
|
|
326
326
|
def load_resource
|
327
|
-
@page = Page.find(params[:id])
|
327
|
+
@page = Page.includes(page_includes).find(params[:id])
|
328
328
|
end
|
329
329
|
|
330
330
|
def pages_from_raw_request
|
@@ -402,6 +402,10 @@ module Alchemy
|
|
402
402
|
def set_preview_mode
|
403
403
|
@preview_mode = true
|
404
404
|
end
|
405
|
+
|
406
|
+
def page_includes
|
407
|
+
Alchemy::EagerLoading.page_includes(version: :draft_version)
|
408
|
+
end
|
405
409
|
end
|
406
410
|
end
|
407
411
|
end
|
@@ -104,7 +104,14 @@ module Alchemy
|
|
104
104
|
# If no index page and no admin users are present we show the "Welcome to Alchemy" page.
|
105
105
|
#
|
106
106
|
def load_index_page
|
107
|
-
@page ||=
|
107
|
+
@page ||= begin
|
108
|
+
Alchemy::Page.
|
109
|
+
contentpages.
|
110
|
+
language_roots.
|
111
|
+
where(language: Language.current).
|
112
|
+
includes(page_includes).
|
113
|
+
first
|
114
|
+
end
|
108
115
|
render template: "alchemy/welcome", layout: false if signup_required?
|
109
116
|
end
|
110
117
|
|
@@ -120,10 +127,13 @@ module Alchemy
|
|
120
127
|
def load_page
|
121
128
|
page_not_found! unless Language.current
|
122
129
|
|
123
|
-
@page ||=
|
124
|
-
|
125
|
-
|
126
|
-
|
130
|
+
@page ||= begin
|
131
|
+
Alchemy::Page.
|
132
|
+
contentpages.
|
133
|
+
where(language: Language.current).
|
134
|
+
includes(page_includes).
|
135
|
+
find_by(urlname: params[:urlname])
|
136
|
+
end
|
127
137
|
end
|
128
138
|
|
129
139
|
def enforce_locale
|
@@ -234,5 +244,9 @@ module Alchemy
|
|
234
244
|
def page_not_found!
|
235
245
|
not_found_error!("Alchemy::Page not found \"#{request.fullpath}\"")
|
236
246
|
end
|
247
|
+
|
248
|
+
def page_includes
|
249
|
+
Alchemy::EagerLoading.page_includes(version: :public_version)
|
250
|
+
end
|
237
251
|
end
|
238
252
|
end
|
@@ -36,6 +36,29 @@ module Alchemy
|
|
36
36
|
element.definition.fetch(:ingredients, []).any?
|
37
37
|
end
|
38
38
|
|
39
|
+
# Returns the translated content/ingredient group for displaying in admin editor group headings
|
40
|
+
#
|
41
|
+
# Translate it in your locale yml file:
|
42
|
+
#
|
43
|
+
# alchemy:
|
44
|
+
# element_groups:
|
45
|
+
# foo: Bar
|
46
|
+
#
|
47
|
+
# Optionally you can scope your ingredient role to an element:
|
48
|
+
#
|
49
|
+
# alchemy:
|
50
|
+
# element_groups:
|
51
|
+
# article:
|
52
|
+
# foo: Baz
|
53
|
+
#
|
54
|
+
def translated_group(group)
|
55
|
+
Alchemy.t(
|
56
|
+
group,
|
57
|
+
scope: "element_groups.#{element.name}",
|
58
|
+
default: Alchemy.t("element_groups.#{group}", default: group.humanize),
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
39
62
|
# CSS classes for the element editor partial.
|
40
63
|
def css_classes
|
41
64
|
[
|
@@ -4,7 +4,10 @@ module Alchemy
|
|
4
4
|
class PublishPageJob < BaseJob
|
5
5
|
queue_as :default
|
6
6
|
|
7
|
-
def perform(
|
7
|
+
def perform(page_id, public_on:)
|
8
|
+
page = Alchemy::Page.includes(
|
9
|
+
Alchemy::EagerLoading.page_includes(version: :draft_version)
|
10
|
+
).find(page_id)
|
8
11
|
Alchemy::Page::Publisher.new(page).publish!(public_on: public_on)
|
9
12
|
end
|
10
13
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Alchemy
|
4
|
+
# Eager loading parameters for loading pages
|
5
|
+
class EagerLoading
|
6
|
+
PAGE_VERSIONS = %i[draft_version public_version]
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# Eager loading parameters for {ActiveRecord::Base.includes}
|
10
|
+
#
|
11
|
+
# Pass this to +includes+ whereever you load an {Alchemy::Page}
|
12
|
+
#
|
13
|
+
# Alchemy::Page.includes(Alchemy::EagerLoading.page_includes).find_by(urlname: "my-page")
|
14
|
+
#
|
15
|
+
# @param version [Symbol] Type of page version to eager load
|
16
|
+
# @return [Array]
|
17
|
+
def page_includes(version: :public_version)
|
18
|
+
raise UnsupportedPageVersion unless version.in? PAGE_VERSIONS
|
19
|
+
|
20
|
+
[
|
21
|
+
:tags,
|
22
|
+
{
|
23
|
+
language: :site,
|
24
|
+
version => {
|
25
|
+
elements: [
|
26
|
+
:page,
|
27
|
+
:touchable_pages,
|
28
|
+
{
|
29
|
+
ingredients: :related_object,
|
30
|
+
contents: :essence,
|
31
|
+
},
|
32
|
+
],
|
33
|
+
},
|
34
|
+
},
|
35
|
+
]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -12,7 +12,9 @@ module Alchemy
|
|
12
12
|
|
13
13
|
# Copies all currently visible elements to the public version of page
|
14
14
|
#
|
15
|
-
# Creates a new published version if none exists yet
|
15
|
+
# Creates a new published version if none exists yet and updates
|
16
|
+
# the `published_at` timestamp of the page.
|
17
|
+
# `published_at` is used as a cache key.
|
16
18
|
#
|
17
19
|
# Sends a publish notification to all registered publish targets
|
18
20
|
#
|
@@ -32,6 +34,7 @@ module Alchemy
|
|
32
34
|
end
|
33
35
|
end
|
34
36
|
end
|
37
|
+
page.update(published_at: public_on)
|
35
38
|
end
|
36
39
|
|
37
40
|
Alchemy.publish_targets.each { |p| p.perform_later(page) }
|
data/app/models/alchemy/page.rb
CHANGED
@@ -457,15 +457,10 @@ module Alchemy
|
|
457
457
|
end
|
458
458
|
end
|
459
459
|
|
460
|
-
# Creates a public version of the page.
|
461
|
-
#
|
462
|
-
# Sets the +published_at+ value to current time
|
463
|
-
#
|
464
|
-
# The +published_at+ attribute is used as +cache_key+.
|
460
|
+
# Creates a public version of the page in the background.
|
465
461
|
#
|
466
462
|
def publish!(current_time = Time.current)
|
467
|
-
|
468
|
-
PublishPageJob.perform_later(self, public_on: current_time)
|
463
|
+
PublishPageJob.perform_later(id, public_on: current_time)
|
469
464
|
end
|
470
465
|
|
471
466
|
# Sets the public_on date on the published version
|
@@ -23,15 +23,44 @@
|
|
23
23
|
html: {id: "element_#{element.id}_form".html_safe, class: 'element-content'} do |f| %>
|
24
24
|
|
25
25
|
<div id="element_<%= element.id %>_errors" class="element_errors"></div>
|
26
|
+
|
27
|
+
<!-- Ingredients -->
|
26
28
|
<% if element.has_ingredients_defined? %>
|
27
29
|
<div class="element-ingredient-editors">
|
28
|
-
<%= render element.ingredients, element_form: f %>
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
<%= render element.ingredients.select { |i| !i.definition[:group] }, element_form: f %>
|
31
|
+
|
32
|
+
<!-- Each ingredient group -->
|
33
|
+
<% element.ingredients.select { |i| i.definition[:group] }.group_by { |i| i.definition[:group] }.each do |group, ingredients| %>
|
34
|
+
<div class="content-group">
|
35
|
+
<%= link_to '#', id: "element_#{element.id}_content_group_#{group.parameterize.underscore}_header", class: 'content-group-header', data: { toggle_content_group: true } do %>
|
36
|
+
<%= element.translated_group group %>
|
37
|
+
<i class="content-group-expand icon fa-fw fa-angle-down fas"></i>
|
38
|
+
<% end %>
|
39
|
+
<%= content_tag :div, id: "element_#{element.id}_content_group_#{group.parameterize.underscore}", class: 'content-group-contents' do %>
|
40
|
+
<%= render ingredients, element_form: f %>
|
41
|
+
<% end %>
|
42
|
+
</div>
|
43
|
+
<% end %>
|
33
44
|
</div>
|
34
45
|
<% end %>
|
46
|
+
<!-- Contents -->
|
47
|
+
<div id="element_<%= element.id %>_content" class="element-content-editors">
|
48
|
+
<%= render element.contents.select { |c| !c.definition[:group] } %>
|
49
|
+
|
50
|
+
<!-- Each content group -->
|
51
|
+
<% element.contents.select { |c| c.definition[:group] }.group_by { |c| c.definition[:group] }.each do |group, contents| %>
|
52
|
+
<div class="content-group">
|
53
|
+
<%= link_to '#', id: "element_#{element.id}_content_group_#{group.parameterize.underscore}_header", class: 'content-group-header', data: { toggle_content_group: true } do %>
|
54
|
+
<%= element.translated_group group %>
|
55
|
+
<i class="content-group-expand icon fa-fw fa-angle-down fas"></i>
|
56
|
+
<% end %>
|
57
|
+
<%= content_tag :div, id: "element_#{element.id}_content_group_#{group.parameterize.underscore}", class: 'content-group-contents' do %>
|
58
|
+
<%= render contents, element_form: f %>
|
59
|
+
<% end %>
|
60
|
+
</div>
|
61
|
+
<% end %>
|
62
|
+
</div>
|
63
|
+
|
35
64
|
<% if element.taggable? %>
|
36
65
|
<div class="autocomplete_tag_list">
|
37
66
|
<%= f.label :tag_list %>
|
data/lib/alchemy/errors.rb
CHANGED
@@ -11,7 +11,7 @@ module Alchemy
|
|
11
11
|
# Raised if no default language configuration can be found.
|
12
12
|
def message
|
13
13
|
"No default language configuration found!" \
|
14
|
-
|
14
|
+
" Please ensure that you have a 'default_language' defined in Alchemy configuration file."
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
@@ -19,7 +19,7 @@ module Alchemy
|
|
19
19
|
# Raised if no default site configuration can be found.
|
20
20
|
def message
|
21
21
|
"No default site configuration found!" \
|
22
|
-
|
22
|
+
" Please ensure that you have a 'default_site' defined in Alchemy configuration file."
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -90,4 +90,10 @@ module Alchemy
|
|
90
90
|
"You need to provide a current_user method in your ApplicationController that returns the current authenticated user."
|
91
91
|
end
|
92
92
|
end
|
93
|
+
|
94
|
+
class UnsupportedPageVersion < StandardError
|
95
|
+
def message
|
96
|
+
"Unknown Version! Please use one of #{Alchemy::EagerLoading::PAGE_VERSIONS.join(", ")}"
|
97
|
+
end
|
98
|
+
end
|
93
99
|
end
|
@@ -16,7 +16,7 @@ module Alchemy::Upgrader::Tasks
|
|
16
16
|
# eager load all elements that have ingredients defined but no ingredient records yet.
|
17
17
|
all_elements = Alchemy::Element
|
18
18
|
.named(elements_with_ingredients.map { |d| d[:name] })
|
19
|
-
.
|
19
|
+
.preload(contents: :essence)
|
20
20
|
.left_outer_joins(:ingredients).where(alchemy_ingredients: { id: nil })
|
21
21
|
.to_a
|
22
22
|
elements_with_ingredients.map do |element_definition|
|
data/lib/alchemy/version.rb
CHANGED
@@ -35,5 +35,29 @@ namespace :alchemy do
|
|
35
35
|
|
36
36
|
puts "Done!"
|
37
37
|
end
|
38
|
+
|
39
|
+
desc "Generates thumbnails for Alchemy Picture Ingredients (set ELEMENTS=element1,element2 to only generate thumbnails for a subset of elements)."
|
40
|
+
task ingredient_picture_thumbnails: :environment do
|
41
|
+
ingredient_pictures = Alchemy::Ingredients::Picture.
|
42
|
+
joins(:element).
|
43
|
+
preload({ related_object: :thumbs }).
|
44
|
+
merge(Alchemy::Element.available)
|
45
|
+
|
46
|
+
if ENV["ELEMENTS"].present?
|
47
|
+
ingredient_pictures = ingredient_pictures.merge(
|
48
|
+
Alchemy::Element.named(ENV["ELEMENTS"].split(","))
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
puts "Regenerate #{ingredient_pictures.count} ingredient picture thumbnails."
|
53
|
+
puts "Please wait..."
|
54
|
+
|
55
|
+
ingredient_pictures.find_each do |ingredient_picture|
|
56
|
+
puts ingredient_picture.picture_url
|
57
|
+
puts ingredient_picture.thumbnail_url
|
58
|
+
end
|
59
|
+
|
60
|
+
puts "Done!"
|
61
|
+
end
|
38
62
|
end
|
39
63
|
end
|
data/package.json
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: alchemy_cms
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.0.
|
4
|
+
version: 6.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas von Deyen
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date: 2022-05-
|
16
|
+
date: 2022-05-30 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: actionmailer
|
@@ -916,6 +916,7 @@ files:
|
|
916
916
|
- app/models/alchemy/base_record.rb
|
917
917
|
- app/models/alchemy/content.rb
|
918
918
|
- app/models/alchemy/content/factory.rb
|
919
|
+
- app/models/alchemy/eager_loading.rb
|
919
920
|
- app/models/alchemy/element.rb
|
920
921
|
- app/models/alchemy/element/definitions.rb
|
921
922
|
- app/models/alchemy/element/element_contents.rb
|