alchemy_cms 4.2.4 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|