alchemy_cms 6.0.0.b1 → 6.0.0.b2

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/alchemy_cms.gemspec +1 -1
  4. data/app/assets/stylesheets/alchemy/archive.scss +14 -0
  5. data/app/controllers/alchemy/admin/attachments_controller.rb +3 -3
  6. data/app/controllers/alchemy/admin/pages_controller.rb +1 -9
  7. data/app/controllers/alchemy/admin/pictures_controller.rb +21 -8
  8. data/app/controllers/alchemy/admin/resources_controller.rb +84 -10
  9. data/app/controllers/alchemy/api/elements_controller.rb +12 -8
  10. data/app/controllers/alchemy/api/pages_controller.rb +4 -2
  11. data/app/models/alchemy/attachment.rb +24 -7
  12. data/app/models/alchemy/element/presenters.rb +18 -1
  13. data/app/models/alchemy/ingredient.rb +5 -0
  14. data/app/models/alchemy/page.rb +10 -1
  15. data/app/models/alchemy/page/page_scopes.rb +4 -0
  16. data/app/models/alchemy/picture.rb +14 -38
  17. data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +1 -1
  18. data/app/views/alchemy/admin/attachments/index.html.erb +2 -3
  19. data/app/views/alchemy/admin/pages/_toolbar.html.erb +1 -1
  20. data/app/views/alchemy/admin/pages/index.html.erb +2 -9
  21. data/app/views/alchemy/admin/partials/_search_form.html.erb +9 -0
  22. data/app/views/alchemy/admin/pictures/_archive.html.erb +1 -1
  23. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +1 -1
  24. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +4 -2
  25. data/app/views/alchemy/admin/pictures/index.html.erb +8 -3
  26. data/app/views/alchemy/admin/resources/_filter.html.erb +12 -0
  27. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +14 -17
  28. data/app/views/alchemy/admin/resources/_form.html.erb +2 -0
  29. data/app/views/alchemy/admin/resources/_table_header.html.erb +15 -0
  30. data/app/views/alchemy/admin/resources/index.html.erb +3 -11
  31. data/config/locales/alchemy.en.yml +85 -49
  32. data/lib/alchemy/forms/builder.rb +21 -1
  33. data/lib/alchemy/resource_filter.rb +40 -0
  34. data/lib/alchemy/resources_helper.rb +1 -16
  35. data/lib/alchemy/test_support/shared_ingredient_examples.rb +21 -3
  36. data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +14 -6
  37. data/lib/alchemy/version.rb +1 -1
  38. data/lib/alchemy_cms.rb +1 -0
  39. data/lib/generators/alchemy/menus/templates/node.html.erb +1 -1
  40. data/lib/generators/alchemy/menus/templates/node.html.haml +1 -1
  41. data/lib/generators/alchemy/menus/templates/node.html.slim +1 -1
  42. data/lib/generators/alchemy/menus/templates/wrapper.html.erb +1 -1
  43. data/lib/generators/alchemy/menus/templates/wrapper.html.haml +1 -1
  44. data/lib/generators/alchemy/menus/templates/wrapper.html.slim +1 -1
  45. data/package.json +1 -1
  46. metadata +10 -9
  47. data/app/views/alchemy/admin/attachments/_filter_bar.html.erb +0 -29
  48. data/app/views/alchemy/admin/pictures/_filter_bar.html.erb +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f491f19d3f42a56aa03e0a82724e1473413c926f9bb9a823f909726415bb844
4
- data.tar.gz: 42c4841d037f5d351305b4e2c97fdc9298a4b886d51b666436c712f1c9c7ce80
3
+ metadata.gz: 50e6a89657c457ab3061bdc6d2cff1eba7ab25856249071e1bbe8c7684e18055
4
+ data.tar.gz: 282dd5e51249513e14309c4d98a09822b2fd7d038f8494f3313e00bc1026f8df
5
5
  SHA512:
6
- metadata.gz: fbb3af41e4d5a9715507f87d38d87f0dea4ceb9c62750721587ce14a7e7aa32f89b7e044fac5b14ab68781539f80b16297cce668b0bef76eb96b099c157b751c
7
- data.tar.gz: fdd0443e75620528d6434eeb13fb675db04f3a91911b92f66122df83c963a737509a99d968ddd48189195340ea00e06310919d3ba201906291088c66ee5ec455
6
+ metadata.gz: 54fc70c8b8ba0305d79d3e6309d4dab7a0a050a3aad1d06014fd3f38f9af34f81970a68358a7c3c222d2d740896dccb905920e6a67ee43bfb226d925916b67e6
7
+ data.tar.gz: 5d5c5ea2794e1f3c2e84a6b99f4eeb07fe284e98dca598c93361ed5f977b3b4b0e966372f2bba1c32f6b5bf21f8f94ec6cb68bd7b375f6bfb899b295bc724e11
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 6.0.0.b2 (2021-08-05)
2
+
3
+ ### Features
4
+
5
+ - Add datepicker simple form input [#2162](https://github.com/AlchemyCMS/alchemy_cms/pull/2162) ([tvdeyen](https://github.com/tvdeyen))
6
+ - Feature flexible resource filters [#2091](https://github.com/AlchemyCMS/alchemy_cms/pull/2091) ([robinboening](https://github.com/robinboening))
7
+
8
+ ### Changes
9
+
10
+ - Require alchemy/version [#2159](https://github.com/AlchemyCMS/alchemy_cms/pull/2159) ([tvdeyen](https://github.com/tvdeyen))
11
+ - Make ingredient examples usable without elements.yml [#2158](https://github.com/AlchemyCMS/alchemy_cms/pull/2158) ([tvdeyen](https://github.com/tvdeyen))
12
+ - Fix ingredient migrator (again) [#2155](https://github.com/AlchemyCMS/alchemy_cms/pull/2155) ([tvdeyen](https://github.com/tvdeyen))
13
+ - Respect additional essence attributes during ingredients migration [#2154](https://github.com/AlchemyCMS/alchemy_cms/pull/2154) ([tvdeyen](https://github.com/tvdeyen))
14
+ - Improve cache key defaults for menus [#2153](https://github.com/AlchemyCMS/alchemy_cms/pull/2153) ([oneiros](https://github.com/oneiros))
15
+ - Make element preview text work with ingredients [#2152](https://github.com/AlchemyCMS/alchemy_cms/pull/2152) ([tvdeyen](https://github.com/tvdeyen))
16
+ - Do not leak all records for guest users in API controllers [#2145](https://github.com/AlchemyCMS/alchemy_cms/pull/2145) ([tvdeyen](https://github.com/tvdeyen))
17
+
18
+ ### Misc
19
+
20
+ - [ruby - main] Upgrade shoulda-matchers to version 5.0.0 [#2161](https://github.com/AlchemyCMS/alchemy_cms/pull/2161) ([depfu](https://github.com/apps/depfu))
21
+
1
22
  ## 6.0.0.b1 (2021-07-05)
2
23
 
3
24
  ### Features
data/alchemy_cms.gemspec CHANGED
@@ -65,7 +65,7 @@ Gem::Specification.new do |gem|
65
65
  gem.add_development_dependency "simplecov", ["~> 0.20"]
66
66
  gem.add_development_dependency "webdrivers", ["~> 4.0"]
67
67
  gem.add_development_dependency "webmock", ["~> 3.3"]
68
- gem.add_development_dependency "shoulda-matchers", ["~> 4.0"]
68
+ gem.add_development_dependency "shoulda-matchers", ["~> 5.0"]
69
69
  gem.add_development_dependency "timecop", ["~> 0.9"]
70
70
 
71
71
  gem.post_install_message = <<~MSG
@@ -2,6 +2,20 @@
2
2
  padding: 2 * $default-padding;
3
3
  }
4
4
 
5
+ .applied-filter {
6
+ display: inline-block;
7
+ padding: 2px 6px;
8
+ border-radius: $default-border-radius;
9
+ border: 1px solid $default-border-color;
10
+ white-space: nowrap;
11
+ font-weight: normal;
12
+
13
+ .dismiss-filter {
14
+ position: relative;
15
+ top: -1px;
16
+ }
17
+ }
18
+
5
19
  .resources-table-wrapper {
6
20
  padding-bottom: 60px;
7
21
 
@@ -21,8 +21,8 @@ module Alchemy
21
21
  @attachments = @attachments.tagged_with(search_filter_params[:tagged_with])
22
22
  end
23
23
 
24
- if search_filter_params[:file_type].present?
25
- @attachments = @attachments.with_file_type(search_filter_params[:file_type])
24
+ if search_filter_params[:filter].present?
25
+ @attachments = apply_filters(@attachments)
26
26
  end
27
27
 
28
28
  @attachments = @attachments
@@ -77,8 +77,8 @@ module Alchemy
77
77
  def search_filter_params
78
78
  @_search_filter_params ||= params.except(*COMMON_SEARCH_FILTER_EXCLUDES + [:attachment]).permit(
79
79
  *common_search_filter_includes + [
80
- :file_type,
81
80
  :form_field_id,
81
+ :content_id,
82
82
  ]
83
83
  )
84
84
  end
@@ -47,11 +47,7 @@ module Alchemy
47
47
  end
48
48
 
49
49
  if search_filter_params[:filter].present?
50
- items = items.public_send(sanitized_filter_params)
51
- end
52
-
53
- if search_filter_params[:page_layout].present?
54
- items = items.where(page_layout: search_filter_params[:page_layout])
50
+ items = apply_filters(items)
55
51
  end
56
52
 
57
53
  items = items.page(params[:page] || 1).per(items_per_page)
@@ -244,10 +240,6 @@ module Alchemy
244
240
  @_resource_handler ||= Alchemy::Resource.new(controller_path, alchemy_module, Alchemy::Page)
245
241
  end
246
242
 
247
- def common_search_filter_includes
248
- super.push(:page_layout, :view)
249
- end
250
-
251
243
  def set_view
252
244
  @view = params[:view] || session[:alchemy_pages_view] || "tree"
253
245
  session[:alchemy_pages_view] = @view
@@ -21,12 +21,7 @@ module Alchemy
21
21
 
22
22
  def index
23
23
  @query = Picture.ransack(search_filter_params[:q])
24
- @pictures = Picture.search_by(
25
- search_filter_params,
26
- @query,
27
- items_per_page,
28
- )
29
- @pictures = @pictures.includes(:thumbs)
24
+ @pictures = filtered_pictures.includes(:thumbs)
30
25
 
31
26
  if in_overlay?
32
27
  archive_overlay
@@ -34,9 +29,11 @@ module Alchemy
34
29
  end
35
30
 
36
31
  def show
37
- @previous = @picture.previous(params)
38
- @next = @picture.next(params)
32
+ @query = Picture.ransack(params[:q])
33
+ @previous = filtered_pictures.where("name < ?", @picture.name).last
34
+ @next = filtered_pictures.where("name > ?", @picture.name).first
39
35
  @assignments = @picture.essence_pictures.joins(content: { element: :page })
36
+
40
37
  render action: "show"
41
38
  end
42
39
 
@@ -130,6 +127,22 @@ module Alchemy
130
127
  redirect_to_index
131
128
  end
132
129
 
130
+ def filtered_pictures
131
+ pictures = @query.result
132
+
133
+ if params[:tagged_with].present?
134
+ pictures = pictures.tagged_with(params[:tagged_with])
135
+ end
136
+
137
+ if search_filter_params[:filter].present?
138
+ pictures = apply_filters(pictures)
139
+ end
140
+
141
+ pictures = pictures.page(params[:page] || 1).per(items_per_page)
142
+
143
+ pictures.order(:name)
144
+ end
145
+
133
146
  def items_per_page
134
147
  if in_overlay?
135
148
  case @size
@@ -3,6 +3,7 @@
3
3
  require "csv"
4
4
  require "alchemy/resource"
5
5
  require "alchemy/resources_helper"
6
+ require "alchemy/resource_filter"
6
7
 
7
8
  module Alchemy
8
9
  module Admin
@@ -13,7 +14,8 @@ module Alchemy
13
14
 
14
15
  helper Alchemy::ResourcesHelper, TagsHelper
15
16
  helper_method :resource_handler, :search_filter_params,
16
- :items_per_page, :items_per_page_options
17
+ :items_per_page, :items_per_page_options, :resource_has_filters,
18
+ :resource_filters_for_select
17
19
 
18
20
  before_action :load_resource,
19
21
  only: [:show, :edit, :update, :destroy]
@@ -34,7 +36,7 @@ module Alchemy
34
36
  end
35
37
 
36
38
  if search_filter_params[:filter].present?
37
- items = items.public_send(sanitized_filter_params)
39
+ items = apply_filters(items)
38
40
  end
39
41
 
40
42
  respond_to do |format|
@@ -90,8 +92,79 @@ module Alchemy
90
92
  @_resource_handler ||= Alchemy::Resource.new(controller_path, alchemy_module)
91
93
  end
92
94
 
95
+ def resource_has_filters
96
+ resource_model.respond_to?(:alchemy_resource_filters)
97
+ end
98
+
99
+ def resource_has_deprecated_filters
100
+ resource_model.alchemy_resource_filters.any? { |f| !f.is_a?(Hash) }
101
+ end
102
+
103
+ def resource_filters
104
+ return unless resource_has_filters
105
+
106
+ @_resource_filters ||= deprecated_resource_filters || resource_model.alchemy_resource_filters
107
+ end
108
+
109
+ def resource_filters_for_select
110
+ resource_filters.map do |filter|
111
+ ResourceFilter.new(filter, resource_handler.resource_name)
112
+ end
113
+ end
114
+
93
115
  protected
94
116
 
117
+ def deprecated_resource_filters
118
+ if resource_has_deprecated_filters
119
+ Alchemy::Deprecation.warn(
120
+ "#{resource_model}.alchemy_resource_filters is using a legacy data structure. " \
121
+ "Please use an Array of Hashes instead. i.e. [{ name: 'foo', values: ['bar', 'baz'] }, ...] " \
122
+ "where values are scopes. With Alchemy 6.1 only the new structure will be supported."
123
+ )
124
+
125
+ @_resource_filters ||= [
126
+ {
127
+ name: :misc,
128
+ values: resource_model.alchemy_resource_filters,
129
+ },
130
+ ]
131
+ end
132
+ end
133
+
134
+ def apply_filters(items)
135
+ sanitize_filter_params!
136
+
137
+ search_filter_params[:filter].each do |filter|
138
+ if argument_scope_filter?(filter)
139
+ items = items.public_send(filter[0], filter[1])
140
+ elsif simple_scope_filter?(filter)
141
+ items = items.public_send(filter[1])
142
+ else
143
+ raise "Can't apply filter #{filter[0]}. Either the name or the values must be defined as class methods / scopes on the model."
144
+ end
145
+ end
146
+
147
+ items
148
+ end
149
+
150
+ def simple_scope_filter?(filter)
151
+ resource_model.respond_to?(filter[1])
152
+ end
153
+
154
+ def argument_scope_filter?(filter)
155
+ resource_model.respond_to?(filter[0])
156
+ end
157
+
158
+ def sanitize_filter_params!
159
+ search_filter_params[:filter].reject! do |_, v|
160
+ eligible_resource_filter_values.exclude?(v)
161
+ end
162
+ end
163
+
164
+ def eligible_resource_filter_values
165
+ resource_filters.map(&:values).flatten
166
+ end
167
+
95
168
  # Returns a translated +flash[:notice]+.
96
169
  # The key should look like "Modelname successfully created|updated|destroyed."
97
170
  def flash_notice_for_resource_action(action = params[:action])
@@ -136,27 +209,28 @@ module Alchemy
136
209
  params.require(resource_handler.namespaced_resource_name).permit!
137
210
  end
138
211
 
139
- def sanitized_filter_params
140
- resource_model.alchemy_resource_filters.detect do |filter|
141
- filter == search_filter_params[:filter]
142
- end || :all
143
- end
144
-
145
212
  def search_filter_params
146
213
  @_search_filter_params ||= params.except(*COMMON_SEARCH_FILTER_EXCLUDES).permit(*common_search_filter_includes).to_h
147
214
  end
148
215
 
149
216
  def common_search_filter_includes
150
- [
217
+ search_filters = [
151
218
  { q: [
152
219
  resource_handler.search_field_name,
153
220
  :s,
154
221
  ] },
155
222
  :tagged_with,
156
- :filter,
157
223
  :page,
158
224
  :per_page,
159
225
  ]
226
+
227
+ if resource_has_filters
228
+ search_filters << {
229
+ filter: resource_filters.map { |f| f[:name] },
230
+ }
231
+ end
232
+
233
+ search_filters
160
234
  end
161
235
 
162
236
  def items_per_page
@@ -9,21 +9,25 @@ module Alchemy
9
9
  # If you want to only load a specific type of element pass ?named=an_element_name
10
10
  #
11
11
  def index
12
- if params[:page_id].present?
13
- @page = Page.find(params[:page_id])
14
- @elements = @page.elements.not_nested
12
+ # Fix for cancancan not able to merge multiple AR scopes for logged in users
13
+ if cannot? :manage, Alchemy::Element
14
+ @elements = Alchemy::Element.accessible_by(current_ability, :index)
15
15
  else
16
- @elements = Element.not_nested.joins(:page_version).merge(PageVersion.published)
16
+ @elements = Alchemy::Element.all
17
17
  end
18
18
 
19
- # Fix for cancancan not able to merge multiple AR scopes for logged in users
20
- if cannot? :manage, Alchemy::Element
21
- @elements = @elements.accessible_by(current_ability, :index)
19
+ @elements = @elements.not_nested.joins(:page_version).merge(PageVersion.published)
20
+
21
+ if params[:page_id].present?
22
+ @elements = @elements.includes(:page).where(alchemy_pages: { id: params[:page_id] })
23
+ else
24
+ @elements = @elements.includes(*element_includes)
22
25
  end
26
+
23
27
  if params[:named].present?
24
28
  @elements = @elements.named(params[:named])
25
29
  end
26
- @elements = @elements.includes(*element_includes).order(:position)
30
+ @elements = @elements.order(:position)
27
31
 
28
32
  render json: @elements, adapter: :json, root: "elements"
29
33
  end
@@ -7,10 +7,12 @@ module Alchemy
7
7
  # Returns all pages as json object
8
8
  #
9
9
  def index
10
- @pages = Language.current&.pages.presence || Alchemy::Page.none
11
10
  # Fix for cancancan not able to merge multiple AR scopes for logged in users
12
11
  if cannot? :edit_content, Alchemy::Page
13
- @pages = @pages.accessible_by(current_ability, :index)
12
+ @pages = Alchemy::Page.accessible_by(current_ability, :index)
13
+ @pages = @pages.where(language: Language.current)
14
+ else
15
+ @pages = Language.current&.pages.presence || Alchemy::Page.none
14
16
  end
15
17
  @pages = @pages.includes(*page_includes)
16
18
  @pages = @pages.ransack(params[:q]).result
@@ -35,6 +35,10 @@ module Alchemy
35
35
  has_many :elements, through: :contents
36
36
  has_many :pages, through: :elements
37
37
 
38
+ scope :by_file_type, ->(file_type) { where(file_mime_type: file_type) }
39
+ scope :recent, -> { where("#{table_name}.created_at > ?", Time.current - 24.hours).order(:created_at) }
40
+ scope :without_tag, -> { left_outer_joins(:taggings).where(gutentag_taggings: { id: nil }) }
41
+
38
42
  # We need to define this method here to have it available in the validations below.
39
43
  class << self
40
44
  # The class used to generate URLs for attachments
@@ -51,6 +55,26 @@ module Alchemy
51
55
  @_url_class = klass
52
56
  end
53
57
 
58
+ def alchemy_resource_filters
59
+ [
60
+ {
61
+ name: :by_file_type,
62
+ values: distinct.pluck(:file_mime_type).map { |type| [Alchemy.t(type, scope: "mime_types"), type] }.sort_by(&:first),
63
+ },
64
+ {
65
+ name: :misc,
66
+ values: %w(recent last_upload without_tag),
67
+ },
68
+ ]
69
+ end
70
+
71
+ def last_upload
72
+ last_id = Attachment.maximum(:id)
73
+ return Attachment.all unless last_id
74
+
75
+ where(id: last_id)
76
+ end
77
+
54
78
  def searchable_alchemy_resource_attributes
55
79
  %w(name file_name)
56
80
  end
@@ -58,13 +82,6 @@ module Alchemy
58
82
  def allowed_filetypes
59
83
  Config.get(:uploader).fetch("allowed_filetypes", {}).fetch("alchemy/attachments", [])
60
84
  end
61
-
62
- def file_types_for_select
63
- file_types = Alchemy::Attachment.pluck(:file_mime_type).uniq.map do |type|
64
- [Alchemy.t(type, scope: "mime_types"), type]
65
- end
66
- file_types.sort_by(&:first)
67
- end
68
85
  end
69
86
 
70
87
  validates_presence_of :file
@@ -46,7 +46,9 @@ module Alchemy
46
46
  # Length of characters after the text will be cut off.
47
47
  #
48
48
  def preview_text(maxlength = 60)
49
- preview_text_from_preview_content(maxlength) || preview_text_from_nested_elements(maxlength)
49
+ preview_text_from_preview_ingredient(maxlength) ||
50
+ preview_text_from_preview_content(maxlength) ||
51
+ preview_text_from_nested_elements(maxlength)
50
52
  end
51
53
 
52
54
  # Generates a preview text containing Element#display_name and Element#preview_text.
@@ -94,6 +96,17 @@ module Alchemy
94
96
  @_preview_content ||= contents.detect(&:preview_content?) || contents.first
95
97
  end
96
98
 
99
+ # The ingredient that's used for element's preview text.
100
+ #
101
+ # It tries to find one of element's ingredients that is defined +as_element_title+.
102
+ # Takes element's first ingredient if no ingredient is defined +as_element_title+.
103
+ #
104
+ # @return (Alchemy::Ingredient)
105
+ #
106
+ def preview_ingredient
107
+ @_preview_ingredient ||= ingredients.detect(&:preview_ingredient?) || ingredients.first
108
+ end
109
+
97
110
  private
98
111
 
99
112
  def preview_text_from_nested_elements(maxlength)
@@ -105,6 +118,10 @@ module Alchemy
105
118
  def preview_text_from_preview_content(maxlength)
106
119
  preview_content.try!(:preview_text, maxlength)
107
120
  end
121
+
122
+ def preview_text_from_preview_ingredient(maxlength)
123
+ preview_ingredient&.preview_text(maxlength)
124
+ end
108
125
  end
109
126
  end
110
127
  end