alchemy_cms 4.2.4 → 4.3.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/FUNDING.yml +4 -0
- data/.travis.yml +8 -6
- data/CHANGELOG.md +15 -10
- data/Gemfile +2 -10
- data/README.md +11 -4
- data/Rakefile +3 -2
- data/alchemy_cms.gemspec +11 -2
- data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +3 -3
- data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +1 -1
- data/app/assets/stylesheets/alchemy/buttons.scss +15 -2
- data/app/assets/stylesheets/alchemy/elements.scss +4 -0
- data/app/assets/stylesheets/alchemy/form_fields.scss +1 -0
- data/app/assets/stylesheets/alchemy/forms.scss +1 -0
- data/app/assets/stylesheets/alchemy/frame.scss +9 -29
- data/app/assets/stylesheets/alchemy/navigation.scss +30 -6
- data/app/assets/stylesheets/alchemy/preview_window.scss +4 -0
- data/app/controllers/alchemy/admin/elements_controller.rb +2 -2
- data/app/controllers/alchemy/pages_controller.rb +9 -4
- data/app/helpers/alchemy/elements_helper.rb +2 -2
- data/app/models/alchemy/element.rb +9 -4
- data/app/models/alchemy/page/page_elements.rb +17 -25
- data/app/models/alchemy/page/page_scopes.rb +1 -1
- data/app/models/alchemy/picture.rb +0 -21
- data/app/views/alchemy/admin/elements/_element.html.erb +2 -1
- data/app/views/alchemy/admin/pages/edit.html.erb +10 -10
- data/lib/alchemy/on_page_layout.rb +1 -1
- data/lib/alchemy/test_support/factories.rb +3 -1
- data/lib/alchemy/test_support/factories/attachment_factory.rb +2 -0
- data/lib/alchemy/test_support/factories/content_factory.rb +5 -0
- data/lib/alchemy/test_support/factories/dummy_user_factory.rb +2 -0
- data/lib/alchemy/test_support/factories/element_factory.rb +3 -0
- data/lib/alchemy/test_support/factories/essence_file_factory.rb +2 -0
- data/lib/alchemy/test_support/factories/essence_picture_factory.rb +2 -0
- data/lib/alchemy/test_support/factories/essence_text_factory.rb +2 -0
- data/lib/alchemy/test_support/factories/language_factory.rb +2 -0
- data/lib/alchemy/test_support/factories/page_factory.rb +2 -0
- data/lib/alchemy/test_support/factories/picture_factory.rb +2 -0
- data/lib/alchemy/test_support/factories/site_factory.rb +2 -0
- data/lib/alchemy/test_support/shared_uploader_examples.rb +1 -1
- data/lib/alchemy/upgrader/tasks/cells_migration.rb +2 -4
- data/lib/alchemy/upgrader/tasks/cells_upgrader.rb +2 -3
- data/lib/alchemy/upgrader/tasks/picture_gallery_upgrader.rb +4 -4
- data/lib/alchemy/version.rb +5 -1
- data/lib/alchemy_cms.rb +0 -1
- data/lib/rails/generators/alchemy/elements/templates/view.html.erb +1 -1
- data/lib/rails/generators/alchemy/elements/templates/view.html.haml +1 -1
- data/lib/rails/generators/alchemy/elements/templates/view.html.slim +1 -1
- data/vendor/assets/javascripts/fileupload/jquery.fileupload-process.js +5 -2
- data/vendor/assets/javascripts/fileupload/jquery.fileupload-validate.js +5 -2
- data/vendor/assets/javascripts/fileupload/jquery.fileupload.js +28 -8
- data/vendor/assets/javascripts/fileupload/jquery.iframe-transport.js +11 -4
- metadata +129 -12
- data/.teatro.yml +0 -8
- data/lib/alchemy/picture_attributes.rb +0 -28
@@ -16,5 +16,9 @@
|
|
16
16
|
|
17
17
|
.collapsed-menu.elements-window-visible & {
|
18
18
|
width: calc(100vw - #{$collapsed-main-menu-width - $default-border-width} - #{$elements-window-width});
|
19
|
+
|
20
|
+
@media screen and (min-width: $large-screen-break-point) {
|
21
|
+
max-width: calc(100vw - #{$collapsed-main-menu-width - $default-border-width} - #{$elements-window-min-width});
|
22
|
+
}
|
19
23
|
}
|
20
24
|
}
|
@@ -8,8 +8,8 @@ module Alchemy
|
|
8
8
|
|
9
9
|
def index
|
10
10
|
@page = Page.find(params[:page_id])
|
11
|
-
@elements = @page.
|
12
|
-
@fixed_elements = @page.
|
11
|
+
@elements = @page.all_elements.not_nested.unfixed.not_trashed
|
12
|
+
@fixed_elements = @page.all_elements.fixed.not_trashed
|
13
13
|
end
|
14
14
|
|
15
15
|
def list
|
@@ -155,10 +155,10 @@ module Alchemy
|
|
155
155
|
end
|
156
156
|
|
157
157
|
def set_expiration_headers
|
158
|
-
if
|
159
|
-
expires_in @page.expiration_time, public: !@page.restricted, must_revalidate: true
|
160
|
-
else
|
158
|
+
if must_not_cache?
|
161
159
|
expires_now
|
160
|
+
else
|
161
|
+
expires_in @page.expiration_time, public: !@page.restricted, must_revalidate: true
|
162
162
|
end
|
163
163
|
end
|
164
164
|
|
@@ -190,12 +190,17 @@ module Alchemy
|
|
190
190
|
# or the cache is stale, because it's been republished by the user.
|
191
191
|
#
|
192
192
|
def render_fresh_page?
|
193
|
-
|
193
|
+
must_not_cache? || stale?(etag: page_etag,
|
194
194
|
last_modified: @page.published_at,
|
195
195
|
public: !@page.restricted,
|
196
196
|
template: 'pages/show')
|
197
197
|
end
|
198
198
|
|
199
|
+
# don't cache pages if we have flash message to display or the page has caching disabled
|
200
|
+
def must_not_cache?
|
201
|
+
flash.present? || !@page.cache_page?
|
202
|
+
end
|
203
|
+
|
199
204
|
def page_not_found!
|
200
205
|
not_found_error!("Alchemy::Page not found \"#{request.fullpath}\"")
|
201
206
|
end
|
@@ -51,7 +51,7 @@ module Alchemy
|
|
51
51
|
#
|
52
52
|
# class MyCustomNewsArchive
|
53
53
|
# def elements(page:)
|
54
|
-
# news_page.elements.
|
54
|
+
# news_page.elements.named('news').order(created_at: :desc)
|
55
55
|
# end
|
56
56
|
#
|
57
57
|
# private
|
@@ -102,7 +102,7 @@ module Alchemy
|
|
102
102
|
|
103
103
|
if options[:from_cell]
|
104
104
|
Alchemy::Deprecation.warn "options[:from_cell] has been removed without replacement. " \
|
105
|
-
"Please `render element.nested_elements
|
105
|
+
"Please `render element.nested_elements` instead."
|
106
106
|
end
|
107
107
|
|
108
108
|
finder = options[:finder] || Alchemy::ElementsFinder.new(options)
|
@@ -63,14 +63,19 @@ module Alchemy
|
|
63
63
|
# In order to get contents in creation order we also order them by id.
|
64
64
|
has_many :contents, -> { order(:position, :id) }, dependent: :destroy
|
65
65
|
|
66
|
-
|
66
|
+
has_many :all_nested_elements,
|
67
|
+
-> { order(:position) },
|
68
|
+
class_name: 'Alchemy::Element',
|
69
|
+
foreign_key: :parent_element_id,
|
70
|
+
dependent: :destroy
|
71
|
+
|
67
72
|
has_many :nested_elements,
|
68
|
-
-> { order(:position).
|
73
|
+
-> { order(:position).available },
|
69
74
|
class_name: 'Alchemy::Element',
|
70
75
|
foreign_key: :parent_element_id,
|
71
76
|
dependent: :destroy
|
72
77
|
|
73
|
-
belongs_to :page, touch: true, inverse_of: :
|
78
|
+
belongs_to :page, touch: true, inverse_of: :all_elements
|
74
79
|
|
75
80
|
# A nested element belongs to a parent element.
|
76
81
|
belongs_to :parent_element,
|
@@ -98,7 +103,7 @@ module Alchemy
|
|
98
103
|
scope :not_restricted, -> { joins(:page).merge(Page.not_restricted) }
|
99
104
|
scope :available, -> { published.not_trashed }
|
100
105
|
scope :named, ->(names) { where(name: names) }
|
101
|
-
scope :excluded, ->(names) { where(
|
106
|
+
scope :excluded, ->(names) { where.not(name: names) }
|
102
107
|
scope :fixed, -> { where(fixed: true) }
|
103
108
|
scope :unfixed, -> { where(fixed: false) }
|
104
109
|
scope :from_current_site, -> { where(Language.table_name => {site_id: Site.current || Site.default}).joins(page: 'language') }
|
@@ -7,26 +7,19 @@ module Alchemy
|
|
7
7
|
included do
|
8
8
|
attr_accessor :autogenerate_elements
|
9
9
|
|
10
|
-
has_many :
|
11
|
-
-> { order(:position)
|
10
|
+
has_many :all_elements,
|
11
|
+
-> { order(:position) },
|
12
12
|
class_name: 'Alchemy::Element'
|
13
|
-
has_many :
|
14
|
-
-> { order(:position).not_nested.
|
13
|
+
has_many :elements,
|
14
|
+
-> { order(:position).not_nested.unfixed.available },
|
15
15
|
class_name: 'Alchemy::Element'
|
16
16
|
has_many :trashed_elements,
|
17
17
|
-> { Element.trashed.order(:position) },
|
18
18
|
class_name: 'Alchemy::Element'
|
19
19
|
has_many :fixed_elements,
|
20
|
-
-> { order(:position).fixed.
|
21
|
-
class_name: 'Alchemy::Element'
|
22
|
-
has_many :descendent_elements,
|
23
|
-
-> { order(:position).unfixed.not_trashed },
|
20
|
+
-> { order(:position).fixed.available },
|
24
21
|
class_name: 'Alchemy::Element'
|
25
22
|
has_many :contents, through: :elements
|
26
|
-
has_many :descendent_contents,
|
27
|
-
through: :descendent_elements,
|
28
|
-
class_name: 'Alchemy::Content',
|
29
|
-
source: :contents
|
30
23
|
has_and_belongs_to_many :to_be_swept_elements, -> { distinct },
|
31
24
|
class_name: 'Alchemy::Element',
|
32
25
|
join_table: ElementToPage.table_name
|
@@ -49,15 +42,12 @@ module Alchemy
|
|
49
42
|
# @return [Array]
|
50
43
|
#
|
51
44
|
def copy_elements(source, target)
|
52
|
-
|
53
|
-
|
54
|
-
|
45
|
+
source_elements = source.all_elements.not_nested.not_trashed
|
46
|
+
source_elements.order(:position).map do |source_element|
|
47
|
+
Element.copy(source_element, {
|
55
48
|
page_id: target.id
|
56
|
-
})
|
57
|
-
new_element.move_to_bottom
|
58
|
-
new_elements << new_element
|
49
|
+
}).tap(&:move_to_bottom)
|
59
50
|
end
|
60
|
-
new_elements
|
61
51
|
end
|
62
52
|
end
|
63
53
|
|
@@ -91,7 +81,8 @@ module Alchemy
|
|
91
81
|
|
92
82
|
return [] if @_element_definitions.blank?
|
93
83
|
|
94
|
-
|
84
|
+
existing_elements = all_elements.not_nested.not_trashed
|
85
|
+
@_existing_element_names = existing_elements.pluck(:name)
|
95
86
|
delete_unique_element_definitions!
|
96
87
|
delete_outnumbered_element_definitions!
|
97
88
|
|
@@ -177,14 +168,14 @@ module Alchemy
|
|
177
168
|
# feed_elements: [element_name, element_2_name]
|
178
169
|
#
|
179
170
|
def feed_elements
|
180
|
-
elements.
|
171
|
+
elements.named(definition['feed_elements'])
|
181
172
|
end
|
182
173
|
|
183
174
|
# Returns an array of all EssenceRichtext contents ids from not folded elements
|
184
175
|
#
|
185
176
|
def richtext_contents_ids
|
186
|
-
|
187
|
-
.where(Element.table_name => {folded: false})
|
177
|
+
Alchemy::Content.joins(:element)
|
178
|
+
.where(Element.table_name => {page_id: id, folded: false})
|
188
179
|
.select(&:has_tinymce?)
|
189
180
|
.collect(&:id)
|
190
181
|
end
|
@@ -196,9 +187,10 @@ module Alchemy
|
|
196
187
|
# And if so, it generates them.
|
197
188
|
#
|
198
189
|
def generate_elements
|
199
|
-
|
190
|
+
existing_elements = all_elements.not_nested.not_trashed
|
191
|
+
existing_element_names = existing_elements.pluck(:name).uniq
|
200
192
|
definition.fetch('autogenerate', []).each do |element_name|
|
201
|
-
next if
|
193
|
+
next if existing_element_names.include?(element_name)
|
202
194
|
Element.create(page: self, name: element_name)
|
203
195
|
end
|
204
196
|
end
|
@@ -66,7 +66,7 @@ module Alchemy
|
|
66
66
|
# Returns all content pages.
|
67
67
|
#
|
68
68
|
scope :contentpages, -> {
|
69
|
-
where(layoutpage: [false, nil]).where(
|
69
|
+
where(layoutpage: [false, nil]).where.not(parent_id: nil)
|
70
70
|
}
|
71
71
|
|
72
72
|
# Returns all public contentpages that are not locked.
|
@@ -246,26 +246,5 @@ module Alchemy
|
|
246
246
|
def image_file_dimensions
|
247
247
|
"#{image_file_width}x#{image_file_height}"
|
248
248
|
end
|
249
|
-
|
250
|
-
# Returns a security token for signed picture rendering requests.
|
251
|
-
#
|
252
|
-
# Pass a params hash containing:
|
253
|
-
#
|
254
|
-
# size [String] (Optional)
|
255
|
-
# crop [Boolean] (Optional)
|
256
|
-
# crop_from [String] (Optional)
|
257
|
-
# crop_size [String] (Optional)
|
258
|
-
# quality [Integer] (Optional)
|
259
|
-
#
|
260
|
-
# to sign them.
|
261
|
-
#
|
262
|
-
def security_token(params = {})
|
263
|
-
params = params.dup.stringify_keys
|
264
|
-
params.update({
|
265
|
-
'crop' => params['crop'] ? 'crop' : nil,
|
266
|
-
'id' => id
|
267
|
-
})
|
268
|
-
PictureAttributes.secure(params)
|
269
|
-
end
|
270
249
|
end
|
271
250
|
end
|
@@ -37,7 +37,8 @@
|
|
37
37
|
class: "nested-elements", data: {
|
38
38
|
'droppable-elements' => element.nestable_elements.join(' ')
|
39
39
|
} do %>
|
40
|
-
<%= render partial: 'alchemy/admin/elements/element',
|
40
|
+
<%= render partial: 'alchemy/admin/elements/element',
|
41
|
+
collection: element.all_nested_elements.not_trashed %>
|
41
42
|
<% end %>
|
42
43
|
|
43
44
|
<% if element.expanded? || element.fixed? %>
|
@@ -13,16 +13,6 @@
|
|
13
13
|
<% end %>
|
14
14
|
</div>
|
15
15
|
<div class="toolbar_spacer"></div>
|
16
|
-
<% unless @page.layoutpage? %>
|
17
|
-
<div class="button_with_label">
|
18
|
-
<%= form_tag alchemy.visit_admin_page_path(@page), id: 'visit_page_form' do %>
|
19
|
-
<button class="icon_button" title="<%= Alchemy.t('Visit page') %>">
|
20
|
-
<%= render_icon('external-link-alt') %>
|
21
|
-
</button>
|
22
|
-
<label><%= Alchemy.t("Visit page") %></label>
|
23
|
-
<% end %>
|
24
|
-
</div>
|
25
|
-
<% end %>
|
26
16
|
<div class="button_with_label">
|
27
17
|
<%= link_to_dialog(
|
28
18
|
render_icon('info-circle'),
|
@@ -76,6 +66,16 @@
|
|
76
66
|
<% end %>
|
77
67
|
</div>
|
78
68
|
<% end %>
|
69
|
+
<% unless @page.layoutpage? %>
|
70
|
+
<div class="button_with_label">
|
71
|
+
<%= form_tag alchemy.visit_admin_page_path(@page), id: 'visit_page_form' do %>
|
72
|
+
<%= button_tag class: 'icon_button', disabled: !@page.public? do %>
|
73
|
+
<%= render_icon('external-link-alt') %>
|
74
|
+
<% end %>
|
75
|
+
<label><%= Alchemy.t("Visit page") %></label>
|
76
|
+
<% end %>
|
77
|
+
</div>
|
78
|
+
<% end %>
|
79
79
|
<% if @page.has_hint? %>
|
80
80
|
<div class="toolbar_spacer"></div>
|
81
81
|
<%= render_hint_for(@page) %>
|
@@ -1,4 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'factory_bot'
|
4
|
+
require 'alchemy/test_support/factories/element_factory'
|
5
|
+
require 'alchemy/test_support/factories/essence_file_factory'
|
6
|
+
require 'alchemy/test_support/factories/essence_picture_factory'
|
2
7
|
require 'alchemy/test_support/factories/essence_text_factory'
|
3
8
|
|
4
9
|
FactoryBot.define do
|
@@ -1,7 +1,7 @@
|
|
1
1
|
RSpec.shared_examples_for "having a json uploader error message" do
|
2
2
|
it "renders json response with error message" do
|
3
3
|
subject
|
4
|
-
expect(response.
|
4
|
+
expect(response.media_type).to eq('application/json')
|
5
5
|
expect(response.status).to eq(422)
|
6
6
|
json = JSON.parse(response.body)
|
7
7
|
expect(json).to have_key('growl_message')
|
@@ -27,13 +27,11 @@ module Alchemy::Upgrader::Tasks
|
|
27
27
|
# bust element definitions insta cache
|
28
28
|
Alchemy::Element.instance_variable_set('@definitions', nil)
|
29
29
|
fixed_element = Alchemy::Element.find_or_initialize_by(fixed: true, name: cell.name, page: cell.page)
|
30
|
-
elements = Alchemy::Element.where(cell_id: cell.id)
|
30
|
+
elements = Alchemy::Element.where(cell_id: cell.id)
|
31
31
|
|
32
32
|
if fixed_element.new_record?
|
33
|
+
fixed_element.nested_elements = elements
|
33
34
|
fixed_element.save!
|
34
|
-
Alchemy::Element.acts_as_list_no_update do
|
35
|
-
elements.update_all(parent_element_id: fixed_element.id)
|
36
|
-
end
|
37
35
|
puts "Created new fixed element '#{fixed_element.name}' for cell '#{cell.name}'."
|
38
36
|
else
|
39
37
|
puts "Element for cell '#{cell.name}' already present. Skip"
|
@@ -114,9 +114,9 @@ module Alchemy::Upgrader::Tasks
|
|
114
114
|
if Dir.exist? cells_view_folder
|
115
115
|
puts "-- Update cell views"
|
116
116
|
Dir.glob("#{cells_view_folder}/*").each do |view|
|
117
|
-
gsub_file(view, /elements\.published/, 'elements
|
117
|
+
gsub_file(view, /elements\.published/, 'elements')
|
118
118
|
gsub_file(view, /cell\.elements(.+)/, 'element.nested_elements\1')
|
119
|
-
gsub_file(view, /render_elements[\(\s]?:?from_cell:?\s?(=>)?\s?cell\)?/, 'render element.nested_elements
|
119
|
+
gsub_file(view, /render_elements[\(\s]?:?from_cell:?\s?(=>)?\s?cell\)?/, 'render element.nested_elements')
|
120
120
|
gsub_file(view, /cell/, 'element')
|
121
121
|
end
|
122
122
|
else
|
@@ -129,7 +129,6 @@ module Alchemy::Upgrader::Tasks
|
|
129
129
|
Dir.glob("#{alchemy_views_folder}/**/*").each do |view|
|
130
130
|
next if File.directory?(view)
|
131
131
|
gsub_file(view, /render_cell[\(\s]?([:'"]?[a-z_]+['"]?)\)?/, 'render_elements(only: \1, fixed: true)')
|
132
|
-
gsub_file(view, /render_elements[\(\s](.*):?from_cell:?\s?(=>)?\s?(['"][a-z_]+['"])\)?/, 'render_elements(\1only: \3, fixed: true)')
|
133
132
|
end
|
134
133
|
end
|
135
134
|
|
@@ -91,22 +91,22 @@ module Alchemy::Upgrader::Tasks
|
|
91
91
|
def find_gallery_pictures_rendering
|
92
92
|
puts '5. Find element views that use gallery pictures:'
|
93
93
|
|
94
|
-
erb_snippet = ' <%= render element.nested_elements
|
94
|
+
erb_snippet = ' <%= render element.nested_elements %>'
|
95
95
|
erb_views = erb_element_partials(:view).select do |view|
|
96
96
|
next if File.read(view).match(GALLERY_PICTURES_ERB_REGEXP).nil?
|
97
97
|
|
98
98
|
inject_into_file view,
|
99
|
-
"<%# TODO: Move the content of next block into its nestable element view and `render element.nested_elements
|
99
|
+
"<%# TODO: Move the content of next block into its nestable element view and `render element.nested_elements` instead %>\n",
|
100
100
|
before: GALLERY_PICTURES_ERB_REGEXP
|
101
101
|
true
|
102
102
|
end
|
103
103
|
|
104
|
-
haml_slim_snippet = ' = element.nested_elements
|
104
|
+
haml_slim_snippet = ' = element.nested_elements'
|
105
105
|
haml_views = haml_slim_element_partials(:view).select do |view|
|
106
106
|
next if File.read(view).match(GALLERY_PICTURES_HAML_REGEXP).nil?
|
107
107
|
|
108
108
|
inject_into_file view,
|
109
|
-
"-# TODO: Move the content of next block into its nestable element view and `render element.nested_elements
|
109
|
+
"-# TODO: Move the content of next block into its nestable element view and `render element.nested_elements` instead\n",
|
110
110
|
before: GALLERY_PICTURES_HAML_REGEXP
|
111
111
|
true
|
112
112
|
end
|