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.

Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +4 -0
  3. data/.travis.yml +8 -6
  4. data/CHANGELOG.md +15 -10
  5. data/Gemfile +2 -10
  6. data/README.md +11 -4
  7. data/Rakefile +3 -2
  8. data/alchemy_cms.gemspec +11 -2
  9. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +3 -3
  10. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +1 -1
  11. data/app/assets/stylesheets/alchemy/buttons.scss +15 -2
  12. data/app/assets/stylesheets/alchemy/elements.scss +4 -0
  13. data/app/assets/stylesheets/alchemy/form_fields.scss +1 -0
  14. data/app/assets/stylesheets/alchemy/forms.scss +1 -0
  15. data/app/assets/stylesheets/alchemy/frame.scss +9 -29
  16. data/app/assets/stylesheets/alchemy/navigation.scss +30 -6
  17. data/app/assets/stylesheets/alchemy/preview_window.scss +4 -0
  18. data/app/controllers/alchemy/admin/elements_controller.rb +2 -2
  19. data/app/controllers/alchemy/pages_controller.rb +9 -4
  20. data/app/helpers/alchemy/elements_helper.rb +2 -2
  21. data/app/models/alchemy/element.rb +9 -4
  22. data/app/models/alchemy/page/page_elements.rb +17 -25
  23. data/app/models/alchemy/page/page_scopes.rb +1 -1
  24. data/app/models/alchemy/picture.rb +0 -21
  25. data/app/views/alchemy/admin/elements/_element.html.erb +2 -1
  26. data/app/views/alchemy/admin/pages/edit.html.erb +10 -10
  27. data/lib/alchemy/on_page_layout.rb +1 -1
  28. data/lib/alchemy/test_support/factories.rb +3 -1
  29. data/lib/alchemy/test_support/factories/attachment_factory.rb +2 -0
  30. data/lib/alchemy/test_support/factories/content_factory.rb +5 -0
  31. data/lib/alchemy/test_support/factories/dummy_user_factory.rb +2 -0
  32. data/lib/alchemy/test_support/factories/element_factory.rb +3 -0
  33. data/lib/alchemy/test_support/factories/essence_file_factory.rb +2 -0
  34. data/lib/alchemy/test_support/factories/essence_picture_factory.rb +2 -0
  35. data/lib/alchemy/test_support/factories/essence_text_factory.rb +2 -0
  36. data/lib/alchemy/test_support/factories/language_factory.rb +2 -0
  37. data/lib/alchemy/test_support/factories/page_factory.rb +2 -0
  38. data/lib/alchemy/test_support/factories/picture_factory.rb +2 -0
  39. data/lib/alchemy/test_support/factories/site_factory.rb +2 -0
  40. data/lib/alchemy/test_support/shared_uploader_examples.rb +1 -1
  41. data/lib/alchemy/upgrader/tasks/cells_migration.rb +2 -4
  42. data/lib/alchemy/upgrader/tasks/cells_upgrader.rb +2 -3
  43. data/lib/alchemy/upgrader/tasks/picture_gallery_upgrader.rb +4 -4
  44. data/lib/alchemy/version.rb +5 -1
  45. data/lib/alchemy_cms.rb +0 -1
  46. data/lib/rails/generators/alchemy/elements/templates/view.html.erb +1 -1
  47. data/lib/rails/generators/alchemy/elements/templates/view.html.haml +1 -1
  48. data/lib/rails/generators/alchemy/elements/templates/view.html.slim +1 -1
  49. data/vendor/assets/javascripts/fileupload/jquery.fileupload-process.js +5 -2
  50. data/vendor/assets/javascripts/fileupload/jquery.fileupload-validate.js +5 -2
  51. data/vendor/assets/javascripts/fileupload/jquery.fileupload.js +28 -8
  52. data/vendor/assets/javascripts/fileupload/jquery.iframe-transport.js +11 -4
  53. metadata +129 -12
  54. data/.teatro.yml +0 -8
  55. 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.elements
12
- @fixed_elements = @page.fixed_elements
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 @page.cache_page?
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
- !@page.cache_page? || stale?(etag: page_etag,
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.available.named('news').order(created_at: :desc)
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.available` instead."
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
- # Elements can have other elements nested inside
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).not_trashed },
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: :descendent_elements
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(arel_table[:name].not_in(names)) }
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 :elements,
11
- -> { order(:position).not_nested.unfixed.not_trashed },
10
+ has_many :all_elements,
11
+ -> { order(:position) },
12
12
  class_name: 'Alchemy::Element'
13
- has_many :elements_including_fixed,
14
- -> { order(:position).not_nested.not_trashed },
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.not_trashed },
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
- new_elements = []
53
- source.elements_including_fixed.each do |source_element|
54
- new_element = Element.copy(source_element, {
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
- @_existing_element_names = elements_including_fixed.pluck(:name)
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.available.named(definition['feed_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
- descendent_contents
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
- elements_already_on_page = elements_including_fixed.pluck(:name)
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 elements_already_on_page.include?(element_name)
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(Page.arel_table[:parent_id].not_eq(nil))
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', collection: element.nested_elements %>
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) %>
@@ -47,7 +47,7 @@ module Alchemy
47
47
  # Registers a callback for given page layout
48
48
  def self.register_callback(page_layout, callback)
49
49
  @callbacks ||= {}
50
- @callbacks[page_layout] ||= []
50
+ @callbacks[page_layout] ||= Set.new
51
51
  @callbacks[page_layout] << callback
52
52
  end
53
53
 
@@ -1,3 +1,5 @@
1
- Dir[File.dirname(__FILE__) + '/factories/*.rb'].each do |file|
1
+ # frozen_string_literal: true
2
+
3
+ Dir["#{File.dirname(__FILE__)}/factories/*.rb"].each do |file|
2
4
  require file
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'factory_bot'
2
4
 
3
5
  FactoryBot.define do
@@ -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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'factory_bot'
2
4
 
3
5
  FactoryBot.define do
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'factory_bot'
4
+ require 'alchemy/test_support/factories/page_factory'
2
5
 
3
6
  FactoryBot.define do
4
7
  factory :alchemy_element, class: 'Alchemy::Element' do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'factory_bot'
2
4
  require 'alchemy/test_support/factories/attachment_factory'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'factory_bot'
2
4
  require 'alchemy/test_support/factories/picture_factory'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'factory_bot'
2
4
 
3
5
  FactoryBot.define do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'factory_bot'
2
4
  require 'alchemy/test_support/factories/site_factory'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'factory_bot'
2
4
  require 'alchemy/test_support/factories/language_factory'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'factory_bot'
2
4
 
3
5
  FactoryBot.define do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'factory_bot'
2
4
 
3
5
  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.content_type).to eq('application/json')
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).order(position: :asc)
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.available')
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.available')
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.available %>'
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.available` instead %>\n",
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.available'
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.available` instead\n",
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