ab_admin 0.9.0 → 0.10.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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/ab_admin/components/in_place_edit.js.coffee +28 -24
  3. data/app/assets/javascripts/ab_admin/core/batch_actions.js.coffee +1 -1
  4. data/app/assets/javascripts/ab_admin/core/init.js.coffee +1 -0
  5. data/app/assets/javascripts/ab_admin/core/ui_utils.js.coffee +21 -1
  6. data/app/assets/javascripts/ab_admin/core/utils.js.coffee +8 -0
  7. data/app/assets/javascripts/ab_admin/inputs/datetime_input.js.coffee +1 -0
  8. data/app/assets/javascripts/ab_admin/main.js +1 -2
  9. data/app/assets/stylesheets/ab_admin/bootstrap_and_overrides.scss +44 -23
  10. data/app/assets/stylesheets/ab_admin/components/_colored_tabs.scss +1 -1
  11. data/app/assets/stylesheets/ab_admin/components/_form.scss +3 -1
  12. data/app/assets/stylesheets/ab_admin/components/_navigation.scss +7 -2
  13. data/app/assets/stylesheets/ab_admin/components/_table_view.scss +75 -9
  14. data/app/assets/stylesheets/ab_admin/components/_tooltip.scss +1 -0
  15. data/app/assets/stylesheets/ab_admin/main.scss +1 -1
  16. data/app/controllers/admin/assets_controller.rb +1 -1
  17. data/app/controllers/admin/base_controller.rb +87 -107
  18. data/app/controllers/admin/manager_controller.rb +17 -47
  19. data/app/views/ab_admin/devise/sessions/new.html.slim +2 -0
  20. data/app/views/admin/assets/batch_edit.html.slim +2 -1
  21. data/app/views/admin/base/_search_layout.html.slim +1 -1
  22. data/app/views/admin/base/create.js.erb +1 -1
  23. data/app/views/admin/base/edit.js.erb +1 -1
  24. data/app/views/admin/base/new.js.erb +1 -1
  25. data/app/views/admin/base/update.js.erb +3 -3
  26. data/app/views/admin/manager/_show_table.html.slim +1 -1
  27. data/app/views/admin/manager/_stats.html.slim +4 -0
  28. data/app/views/admin/manager/_table.html.slim +6 -3
  29. data/app/views/admin/shared/_content_actions.html.slim +22 -15
  30. data/app/views/admin/shared/_save_buttons.html.slim +10 -1
  31. data/app/views/admin/users/_form.html.slim +2 -2
  32. data/config/locales/en.yml +8 -13
  33. data/config/locales/it.yml +1 -0
  34. data/db/migrate/20130101000001_create_users.rb +1 -4
  35. data/db/migrate/20130101000003_create_assets.rb +1 -1
  36. data/db/migrate/20130101000004_create_headers.rb +5 -5
  37. data/db/migrate/20130101000005_create_static_pages.rb +2 -5
  38. data/db/migrate/20130101000006_create_structures.rb +1 -1
  39. data/db/migrate/20130101000007_base_translations.rb +43 -12
  40. data/db/migrate/20130101000008_create_admin_comments.rb +2 -7
  41. data/db/migrate/20130101000009_create_tracks.rb +4 -8
  42. data/lib/ab_admin/abstract_resource.rb +6 -5
  43. data/lib/ab_admin/carrierwave/base_uploader.rb +87 -74
  44. data/lib/ab_admin/carrierwave/glue.rb +0 -5
  45. data/lib/ab_admin/concerns/admin_addition.rb +19 -1
  46. data/lib/ab_admin/concerns/utilities.rb +1 -1
  47. data/lib/ab_admin/config/base.rb +20 -4
  48. data/lib/ab_admin/core_ext/array.rb +0 -5
  49. data/lib/ab_admin/core_ext/string.rb +1 -6
  50. data/lib/ab_admin/devise.rb +7 -0
  51. data/lib/ab_admin/engine.rb +1 -1
  52. data/lib/ab_admin/hooks/ckeditor_lazy.rb +13 -0
  53. data/lib/ab_admin/menu/base_group.rb +0 -1
  54. data/lib/ab_admin/menu/group.rb +2 -4
  55. data/lib/ab_admin/menu/item.rb +4 -8
  56. data/lib/ab_admin/models/asset.rb +7 -10
  57. data/lib/ab_admin/models/locator.rb +1 -1
  58. data/lib/ab_admin/models/settings.rb +2 -2
  59. data/lib/ab_admin/models/structure.rb +3 -3
  60. data/lib/ab_admin/models/track.rb +15 -3
  61. data/lib/ab_admin/utils/csv_document.rb +4 -4
  62. data/lib/ab_admin/utils/eval_helpers.rb +0 -16
  63. data/lib/ab_admin/utils/logger.rb +12 -2
  64. data/lib/ab_admin/utils/mysql.rb +2 -3
  65. data/lib/ab_admin/utils/xls_document.rb +1 -3
  66. data/lib/ab_admin/utils.rb +0 -5
  67. data/lib/ab_admin/version.rb +1 -1
  68. data/lib/ab_admin/views/admin_helpers.rb +33 -16
  69. data/lib/ab_admin/views/admin_navigation_helpers.rb +12 -9
  70. data/lib/ab_admin/views/inputs/ckeditor_input.rb +1 -5
  71. data/lib/ab_admin/views/manager_helpers.rb +9 -3
  72. data/lib/ab_admin/views/search_form_builder.rb +12 -12
  73. data/lib/ab_admin.rb +13 -2
  74. data/lib/generators/ab_admin/install/templates/models/user.rb +1 -2
  75. data/lib/generators/ab_admin/install/templates/uploaders/attachment_file_uploader.rb +1 -1
  76. data/lib/generators/ab_admin/install/templates/uploaders/avatar_uploader.rb +1 -1
  77. data/lib/generators/ab_admin/install/templates/uploaders/picture_uploader.rb +16 -3
  78. data/lib/generators/ab_admin/resource/resource_generator.rb +0 -4
  79. data/lib/generators/ab_admin/resource/templates/controller.erb +0 -7
  80. data/lib/tasks/assets.rake +5 -5
  81. metadata +28 -26
@@ -18,22 +18,13 @@ en:
18
18
  actions:
19
19
  activate:
20
20
  link: Activate
21
- batch_destroy:
22
- confirmation: Are you sure you want to delete the selected records?
23
- link: Delete selected
24
- title: Multi-remove
25
- batch_publish:
26
- link: Publish selected
27
- title: Multi-publish
28
- batch_un_publish:
29
- link: Unpublish selected
30
- title: Multi-hiding
31
21
  create:
32
22
  link: Create
33
23
  title: Creation
34
24
  destroy:
35
- link: Remove
36
- title: Removal
25
+ link: Destroy
26
+ title: Destroy
27
+ confirmation: Are you sure you want to delete the selected records?
37
28
  edit:
38
29
  link: Edit
39
30
  title: Editing
@@ -94,7 +85,7 @@ en:
94
85
  comments:
95
86
  add: Comment
96
87
  title_content: Comments (%{count})
97
- delete: Remove
88
+ delete: Destroy
98
89
  delete_confirmation: Are you sure you want to delete this?
99
90
  empty: Empty
100
91
  exit: Output
@@ -109,6 +100,8 @@ en:
109
100
  form:
110
101
  cancel: Cancel
111
102
  save: Save
103
+ force_save: Force save
104
+ refresh: Refresh
112
105
  save_and_add_another: Save and add new
113
106
  save_and_edit: Save and continue editing
114
107
  save_and_edit_next: Save and next.
@@ -245,6 +238,7 @@ en:
245
238
  updater_id: 'Updater'
246
239
  remember_me: Remember
247
240
  password: Password
241
+ otp_attempt: OTP
248
242
  flash:
249
243
  admin:
250
244
  actions:
@@ -290,5 +284,6 @@ en:
290
284
  size_too_big: is too big (should be at most %{file_size})
291
285
  carrierwave_processing_error: Cannot resize image.
292
286
  carrierwave_integrity_error: Not an image.
287
+ changed: 'Record was changed by someone else, "Refresh" to get the latest version and repeat changes or "Force save" to overwrite'
293
288
  unauthorized:
294
289
  default: You are not authorized to access this page.
@@ -103,6 +103,7 @@ it:
103
103
  form:
104
104
  cancel: Cancellazione
105
105
  save: Salvare
106
+ force_save: Force save
106
107
  save_and_add_another: Salva e aggiungere nuovi
107
108
  save_and_edit: Salva e continuare a modificare
108
109
  save_and_edit_next: Salva e traccia.
@@ -1,4 +1,4 @@
1
- class CreateUsers < ActiveRecord::Migration
1
+ class CreateUsers < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table(:users) do |t|
4
4
  t.string :login
@@ -35,9 +35,6 @@ class CreateUsers < ActiveRecord::Migration
35
35
  t.string :current_sign_in_ip
36
36
  t.string :last_sign_in_ip
37
37
 
38
- ## Encryptable
39
- t.string :password_salt
40
-
41
38
  ## Confirmable
42
39
  t.string :confirmation_token
43
40
  t.datetime :confirmed_at
@@ -1,4 +1,4 @@
1
- class CreateAssets < ActiveRecord::Migration
1
+ class CreateAssets < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table :assets do |t|
4
4
  t.string :data_file_name, null: false
@@ -1,12 +1,12 @@
1
- class CreateHeaders < ActiveRecord::Migration
1
+ class CreateHeaders < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table :headers do |t|
4
- t.string :headerable_type, limit: 50, null: false
5
- t.integer :headerable_id, null: false
6
-
4
+ t.string :headerable_type, limit: 50, null: false
5
+ t.integer :headerable_id, null: false
6
+
7
7
  t.timestamps
8
8
  end
9
-
9
+
10
10
  add_index :headers, [:headerable_type, :headerable_id], unique: true
11
11
  end
12
12
  end
@@ -1,14 +1,11 @@
1
- class CreateStaticPages < ActiveRecord::Migration
1
+ class CreateStaticPages < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table :static_pages do |t|
4
- t.integer :structure_id, null: false
4
+ t.references :structure, null: false
5
5
  t.references :user
6
6
  t.boolean :is_visible, default: true, null: false
7
7
 
8
8
  t.timestamps
9
9
  end
10
-
11
- add_index :static_pages, :user_id
12
- add_index :static_pages, :structure_id
13
10
  end
14
11
  end
@@ -1,4 +1,4 @@
1
- class CreateStructures < ActiveRecord::Migration
1
+ class CreateStructures < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table :structures do |t|
4
4
  t.string :slug, null: false
@@ -1,15 +1,46 @@
1
- class BaseTranslations < ActiveRecord::Migration
2
- def self.up
3
- StaticPage.create_translation_table! title: :string, content: :text
4
- Header.create_translation_table! title: :string, h1: :string, keywords: :string, description: :text, seo_block: :text
5
- Structure.create_translation_table! title: :string, redirect_url: :string
6
- Asset.create_translation_table! name: :string, alt: :string
7
- end
1
+ class BaseTranslations < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :static_page_translations do |t|
4
+ t.references :static_page, null: false
5
+ t.string :locale, limit: 5, null: false
6
+ t.string :title
7
+ t.text :content
8
+
9
+ t.timestamps
10
+ end
11
+ add_index :static_page_translations, [:static_page_id, :locale], unique: true, name: 'static_pages_ts_static_page_id_locale'
12
+
13
+ create_table :header_translations do |t|
14
+ t.references :header, null: false
15
+ t.string :locale, limit: 5, null: false
16
+ t.string :title
17
+ t.string :h1
18
+ t.string :keywords
19
+ t.text :description
20
+ t.text :seo_block
21
+
22
+ t.timestamps
23
+ end
24
+ add_index :header_translations, [:header_id, :locale], unique: true, name: 'headers_ts_header_id_locale'
25
+
26
+ create_table :structure_translations do |t|
27
+ t.references :structure, null: false
28
+ t.string :locale, limit: 5, null: false
29
+ t.string :title
30
+ t.string :redirect_url
31
+
32
+ t.timestamps
33
+ end
34
+ add_index :structure_translations, [:structure_id, :locale], unique: true, name: 'structures_ts_structure_id_locale'
35
+
36
+ create_table :asset_translations do |t|
37
+ t.references :asset, null: false
38
+ t.string :locale, limit: 5, null: false
39
+ t.string :name
40
+ t.string :alt
8
41
 
9
- def self.down
10
- StaticPage.drop_translation_table!
11
- Header.drop_translation_table!
12
- Structure.drop_translation_table!
13
- Asset.drop_translation_table!
42
+ t.timestamps
43
+ end
44
+ add_index :asset_translations, [:asset_id, :locale], unique: true, name: 'assets_ts_asset_id_locale'
14
45
  end
15
46
  end
@@ -1,18 +1,13 @@
1
- class CreateAdminComments < ActiveRecord::Migration
1
+ class CreateAdminComments < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table :admin_comments do |t|
4
4
  t.references :user
5
5
  t.string :user_name
6
- t.integer :resource_id, null: false
7
- t.string :resource_type, limit: 50, null: false
6
+ t.references :resource, polymorphic: true
8
7
  t.references :resource_user
9
8
  t.text :body
10
9
 
11
10
  t.timestamps
12
11
  end
13
-
14
- add_index :admin_comments, :user_id
15
- add_index :admin_comments, :resource_user_id
16
- add_index :admin_comments, [:resource_type, :resource_id]
17
12
  end
18
13
  end
@@ -1,20 +1,16 @@
1
- class CreateTracks < ActiveRecord::Migration
1
+ class CreateTracks < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table :tracks do |t|
4
4
  t.string :name
5
5
  t.string :key
6
- t.belongs_to :trackable, polymorphic: true
7
- t.belongs_to :user
8
- t.belongs_to :owner
6
+ t.references :trackable, polymorphic: true
7
+ t.references :user
8
+ t.references :owner
9
9
  t.column :trackable_changes, :mediumtext
10
10
  t.text :parameters
11
11
 
12
12
  t.timestamps
13
13
  end
14
-
15
- add_index :tracks, [:trackable_type, :trackable_id]
16
- add_index :tracks, :owner_id
17
- add_index :tracks, :user_id
18
14
  add_index :tracks, :key
19
15
  end
20
16
  end
@@ -6,14 +6,15 @@ module AbAdmin
6
6
  ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy, :preview, :batch, :rebuild, :custom_action, :history]
7
7
  end
8
8
 
9
- attr_accessor :model, :table, :search, :export, :form, :chart, :map, :modal_form, :show, :preview_path, :actions, :custom_settings,
10
- :batch_action_list, :action_items, :disabled_action_items, :resource_action_items, :tree_node_renderer,
9
+ attr_accessor :model, :table, :search, :export, :form, :chart, :stats, :map, :modal_form, :show,
10
+ :preview_path, :actions, :custom_settings,
11
+ :batch_actions, :action_items, :disabled_action_items, :resource_action_items, :tree_node_renderer,
11
12
  :parent_resources, :custom_actions, :permitted_params, :scopes
12
13
 
13
14
  def initialize
14
15
  @actions = ACTIONS
15
16
  @custom_settings = {}
16
- @batch_action_list = [AbAdmin::Config::BatchAction.new(:destroy, confirm: I18n.t('admin.delete_confirmation'))]
17
+ @batch_actions= [AbAdmin::Config::BatchAction.new(:destroy, confirm: I18n.t('admin.delete_confirmation'))]
17
18
  @action_items = []
18
19
  @disabled_action_items = []
19
20
  @default_action_items_for = {}
@@ -87,9 +88,9 @@ module AbAdmin
87
88
 
88
89
  def batch_action(name, options={}, &block)
89
90
  if options
90
- instance.batch_action_list << AbAdmin::Config::BatchAction.new(name.to_sym, options, &block)
91
+ instance.batch_actions << AbAdmin::Config::BatchAction.new(name.to_sym, options, &block)
91
92
  else
92
- instance.batch_action_list.reject!{|a| a.name == name.to_sym }
93
+ instance.batch_actions.reject!{|a| a.name == name.to_sym }
93
94
  end
94
95
  end
95
96
 
@@ -7,8 +7,7 @@ module AbAdmin
7
7
  include ::CarrierWave::MiniMagick
8
8
  include AbAdmin::Utils::EvalHelpers
9
9
 
10
- class_attribute :transliterate, :human_filenames
11
- self.transliterate = true
10
+ class_attribute :human_filenames
12
11
  self.human_filenames = true
13
12
 
14
13
  attr_accessor :internal_identifier
@@ -25,41 +24,48 @@ module AbAdmin
25
24
 
26
25
  process :set_model_info
27
26
 
28
- def strict_filename(for_file=filename)
29
- "#{version_name || secure_token}#{File.extname(for_file).downcase}"
27
+ def filename
28
+ "#{[human_part, secure_token].compact.join('_')}#{extension}"
30
29
  end
31
30
 
32
- def save_original_name(file)
33
- model.original_name ||= file.original_filename if file.respond_to?(:original_filename)
31
+ def full_filename(*)
32
+ return filename unless version_name
33
+ base = "#{version_filename_part}#{version_extension}"
34
+ return base unless human_filenames
35
+ [human_part, base].compact.join('_')
34
36
  end
35
37
 
36
- def base_filename_part
37
- return if version_name == :default
38
- return secure_token unless version_name
39
- version_name.to_s.start_with?('retina_') ? "#{version_name.to_s.sub(/^retina_/, '')}@2x" : version_name.to_s
38
+ def human_part
39
+ normalize_filename(model.send("#{mounted_as}_file_name").to_s.strip.remove(/\.\w+$/)).remove(secure_token).chomp('_').presence
40
40
  end
41
41
 
42
- def full_filename(for_file=filename)
43
- human_filenames ? human_full_filename(for_file) : strict_filename(for_file)
42
+ def extension
43
+ File.extname(model.original_name).downcase
44
44
  end
45
45
 
46
- def human_full_filename(for_file=filename)
47
- ext = File.extname(for_file)
48
- system_part = base_filename_part
49
- human_filename_part = for_file.chomp(ext)
50
- return "#{system_part || version_name}#{ext}" if human_filename_part == secure_token
51
- system_part ? "#{human_filename_part}_#{system_part}#{ext}" : "#{human_filename_part}#{ext}"
46
+ def version_extension
47
+ webp? ? '.webp' : extension
52
48
  end
53
49
 
54
- def full_original_filename
55
- "#{base_filename_part}#{File.extname(store_filename)}"
50
+ def version_filename_part
51
+ return secure_token unless version_name
52
+ strict_version_name = version_name.to_s.remove('retina_').remove('_webp')
53
+ strict_version_name = nil if strict_version_name.to_sym == :default
54
+ "#{strict_version_name}#{'@2x' if retina?}"
56
55
  end
57
56
 
58
- # use secure token in the filename for non processed image
59
57
  def secure_token
60
58
  model.data_secure_token ||= AbAdmin.friendly_token(20).downcase
61
59
  end
62
60
 
61
+ def retina?
62
+ version_name.to_s.start_with?('retina_')
63
+ end
64
+
65
+ def webp?
66
+ version_name.to_s.end_with?('_webp')
67
+ end
68
+
63
69
  def store_model_filename(record)
64
70
  old_file_name = filename
65
71
  new_file_name = model_filename(old_file_name, record)
@@ -67,69 +73,81 @@ module AbAdmin
67
73
  rename_via_move(new_file_name)
68
74
  end
69
75
 
70
- alias_method :store_filename, :filename
71
-
72
- def filename
73
- internal_identifier || model.send("#{mounted_as}_file_name") || (store_filename && "#{secure_token}#{File.extname(store_filename).downcase}")
74
- end
75
-
76
- def write_internal_identifier(internal_identifier)
77
- self.internal_identifier = internal_identifier
78
- versions.values.each{|v| v.internal_identifier = internal_identifier }
76
+ def save_original_name(file)
77
+ model.original_name ||= file.original_filename if file.respond_to?(:original_filename)
79
78
  end
80
79
 
81
- # transliterate original filename
82
- # allow to build custom filename
83
80
  def model_filename(base_filename, record)
84
81
  custom_file_name = model.build_filename(base_filename, record)
85
82
  return unless custom_file_name
86
- normalize_filename(custom_file_name) + File.extname(base_filename).downcase
83
+ normalize_filename(custom_file_name)
87
84
  end
85
+ private :model_filename
88
86
 
89
87
  def normalize_filename(raw_filename)
90
- parameterize_args = ActiveSupport::VERSION::MAJOR > 4 ? {separator: '_'} : '_'
91
- I18n.transliterate(raw_filename).parameterize(**parameterize_args).gsub(/[\-_]+/, '_').downcase
92
- end
93
-
94
- # rename files via move
95
- def rename_via_move(new_file_name)
96
- if human_filenames
97
- dir = File.dirname(path)
98
-
99
- moves = []
100
- versions.values.unshift(self).each do |v|
101
- from_path = File.join(dir, v.full_filename)
102
- to_path = File.join(dir, v.full_filename(new_file_name))
103
- next if from_path == to_path || !File.exists?(from_path)
104
- moves << [from_path, to_path]
105
- end
106
- moves.each { |move| FileUtils.mv(*move) }
88
+ I18n.transliterate(raw_filename.unicode_normalize).parameterize(separator: '_').gsub(/[\-_]+/, '_').downcase
89
+ end
90
+
91
+ def rename_via_move(new_filename)
92
+ dir = File.dirname(path)
93
+ old_names = versions.values.unshift(self).map(&:full_filename)
94
+ model.send("#{mounted_as}_file_name=", "#{[new_filename.presence, secure_token].compact.join('_')}#{extension}")
95
+ new_names = versions.values.unshift(self).map(&:full_filename)
96
+ old_names.zip(new_names).each do |old_name, new_name|
97
+ old_path, new_path = File.join(dir, old_name), File.join(dir, new_name)
98
+ next if old_path == new_path || !File.exist?(old_path)
99
+ FileUtils.mv(old_path, new_path)
107
100
  end
108
-
109
- write_internal_identifier new_file_name
110
- model.send("write_#{mounted_as}_identifier")
111
- retrieve_from_store!(new_file_name) if human_filenames
112
-
113
- new_file_name
101
+ retrieve_from_store!(model.send("#{mounted_as}_file_name"))
114
102
  end
115
103
 
116
- private :write_internal_identifier, :store_filename, :model_filename
117
-
118
- def rmagick_included?
119
- self.class.included_modules.map(&:to_s).include?('CarrierWave::RMagick')
120
- end
121
-
122
- # prevent large number of subdirectories
123
104
  def store_dir
124
105
  str_id = model.id.to_s.rjust(4, '0')
125
106
  [AbAdmin.uploads_dir, model.class.to_s.underscore, str_id[0..2], str_id[3..-1]].join('/')
126
107
  end
127
108
 
109
+ def convert_to_webp(options = {})
110
+ webp_path = "#{File.dirname(path)}/#{full_filename}"
111
+ WebP.encode(path, webp_path, options_for_webp(options))
112
+ @file = ::CarrierWave::SanitizedFile.new(tempfile: webp_path, filename: webp_path, content_type: 'image/webp')
113
+ end
114
+
115
+ def options_for_webp(options)
116
+ w, h = width, height
117
+ options = options.dup
118
+ ratio = w.to_f / h
119
+ if options[:resize_to_fill]
120
+ res_w, res_h = options[:resize_to_fill]
121
+ res_ratio = res_w.to_f / res_h
122
+ options.update(resize_w: res_w, resize_h: res_h) unless w == res_w && h == res_h
123
+ if ratio > res_ratio
124
+ crop_res_w = h * res_ratio
125
+ crop_res_h = h
126
+ options.update(crop_x: ((w - crop_res_w) / 2).to_i, crop_y: 0, crop_w: crop_res_w.to_i, crop_h: crop_res_h.to_i)
127
+ elsif ratio < res_ratio
128
+ crop_res_w = w
129
+ crop_res_h = w / res_ratio
130
+ options.update(crop_x: 0, crop_y: ((h - crop_res_h) / 2).to_i, crop_w: crop_res_w.to_i, crop_h: crop_res_h.to_i)
131
+ end
132
+ elsif options[:resize_to_fit]
133
+ res_w, res_h = options[:resize_to_fit]
134
+ res_ratio = res_w.to_f / res_h
135
+ if ratio == res_ratio
136
+ options.update(resize_w: res_w, resize_h: res_h) unless w == res_w && h == res_h
137
+ elsif ratio > res_ratio
138
+ options.update(resize_w: res_h)
139
+ elsif ratio < res_ratio
140
+ options.update(resize_h: res_w)
141
+ end
142
+ end
143
+ options.except(:resize_to_fill, :resize_to_fit)
144
+ end
145
+
128
146
  # Strips out all embedded information from the image
129
147
  # process :strip
130
148
  #
131
149
  def strip
132
- manipulate! do |img|
150
+ minimagick! do |img|
133
151
  img.strip
134
152
  img = yield(img) if block_given?
135
153
  img
@@ -143,7 +161,7 @@ module AbAdmin
143
161
  percentage = normalize_param(percentage)
144
162
 
145
163
  unless percentage.blank?
146
- manipulate! do |img|
164
+ minimagick! do |img|
147
165
  img.quality percentage.to_s
148
166
  img = yield(img) if block_given?
149
167
  img
@@ -159,7 +177,7 @@ module AbAdmin
159
177
 
160
178
  unless degrees.blank?
161
179
  manipulate! do |img|
162
- rmagick_included? ? img.rotate!(degrees.to_i) : img.rotate(degrees.to_s)
180
+ self.class.included_modules.map(&:to_s).include?('CarrierWave::RMagick') ? img.rotate!(degrees.to_i) : img.rotate(degrees.to_s)
163
181
  img = yield(img) if block_given?
164
182
  img
165
183
  end
@@ -175,7 +193,7 @@ module AbAdmin
175
193
  geometry = normalize_param(geometry[0]) if geometry.size == 1
176
194
 
177
195
  if geometry && geometry.size == 4
178
- manipulate! do |img|
196
+ minimagick! do |img|
179
197
  img.crop '%ix%i+%i+%i' % geometry
180
198
  img = yield(img) if block_given?
181
199
  img
@@ -184,7 +202,7 @@ module AbAdmin
184
202
  end
185
203
 
186
204
  def watermark(watermark_path, gravity='SouthEast')
187
- manipulate! do |img|
205
+ minimagick! do |img|
188
206
  resolved_path = watermark_path.is_a?(Symbol) ? send(watermark_path) : watermark_path
189
207
  watermark_image = ::MiniMagick::Image.open(resolved_path)
190
208
  img.composite(watermark_image) { |c| c.gravity gravity }
@@ -201,12 +219,7 @@ module AbAdmin
201
219
  end
202
220
 
203
221
  def dimensions
204
- [magick[:width], magick[:height]]
205
- end
206
-
207
- def magick
208
- #@magick ||= ::MiniMagick::Image.new(current_path)
209
- ::MiniMagick::Image.new(current_path)
222
+ [width, height]
210
223
  end
211
224
 
212
225
  protected
@@ -11,11 +11,6 @@ module AbAdmin
11
11
  mount_uploader(:data, uploader, options, &block)
12
12
  end
13
13
 
14
- def sunrise_uploader(*args)
15
- ActiveSupport::Deprecation.warn('`sunrise_uploader` is deprecated, use `ab_admin_uploader` instead')
16
- ab_admin_uploader(*args)
17
- end
18
-
19
14
  def validates_filesize_of(*attr_names)
20
15
  validates_with FileSizeValidator, _merge_attributes(attr_names)
21
16
  end
@@ -4,14 +4,25 @@ module AbAdmin
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
+ attr_accessor :last_updated_timestamp
8
+ validate :do_not_overwrite, if: :last_updated_timestamp
7
9
  scope(:admin, proc { all }) unless respond_to?(:admin)
8
10
  scope(:base, -> { all }) unless respond_to?(:base)
9
- scope :by_ids, lambda { |ids| where("#{quoted_table_name}.id IN (?)", AbAdmin.val_to_array(ids).push(0)) } unless respond_to?(:by_ids)
10
11
 
11
12
  class_attribute :batch_actions, instance_writer: false
12
13
  self.batch_actions = [:destroy]
13
14
  end
14
15
 
16
+ def updated_timestamp(associations: true)
17
+ res = [updated_at]
18
+ res += translations.map(&:updated_at) if self.class.translates?
19
+ if associations
20
+ associations = self.class.nested_attributes_options.keys unless associations.is_a?(Array)
21
+ res += associations.flat_map{|assoc| Array(send(assoc)).map(&:updated_timestamp) }
22
+ end
23
+ res.compact.max
24
+ end
25
+
15
26
  def for_input_token
16
27
  {id: id, text: AbAdmin.safe_display_name(self).to_s}
17
28
  end
@@ -63,6 +74,13 @@ module AbAdmin
63
74
  sql = "(#{quoted_order_col} #{predicate} :val OR (#{quoted_order_col} = :val AND #{self.class.quote_column('id')} #{id_predicate} #{id}))"
64
75
  scope.where(sql, val: send(order_col)).ransack(query[:q]).result(distinct: true).first
65
76
  end
77
+
78
+ private
79
+
80
+ def do_not_overwrite
81
+ return if new_record? || last_updated_timestamp.blank?
82
+ errors.add(:base, :changed) if updated_timestamp.to_i > last_updated_timestamp.to_i
83
+ end
66
84
  end
67
85
  end
68
86
  end
@@ -70,7 +70,7 @@ module AbAdmin
70
70
  add_cond << assoc.klass.instance_exec(&assoc.scope).to_sql[/WHERE(.*?)(?:(?:ORDER|LIMIT).*)?$/, 1] if assoc.scope
71
71
  if assoc.klass.default_scopes.present?
72
72
  assoc.klass.default_scopes.each do |scope|
73
- add_cond << scope.call.to_sql[/WHERE(.*?)(?:(?:ORDER|LIMIT).*)?$/, 1]
73
+ add_cond << scope.scope.call.to_sql[/WHERE(.*?)(?:(?:ORDER|LIMIT).*)?$/, 1]
74
74
  end
75
75
  end
76
76
  count_klass = assoc_count.klass
@@ -32,6 +32,11 @@ module AbAdmin
32
32
 
33
33
  class Table < BaseBuilder
34
34
  self.partial_name = 'table'
35
+
36
+ def row_html_class
37
+ return :row_html_class if options[:row_html_class].is_a?(TrueClass)
38
+ options[:row_html_class]
39
+ end
35
40
  end
36
41
 
37
42
  class Search < BaseBuilder
@@ -42,6 +47,10 @@ module AbAdmin
42
47
  self.partial_name = 'chart'
43
48
  end
44
49
 
50
+ class Stats < BaseBuilder
51
+ self.partial_name = 'stats'
52
+ end
53
+
45
54
  class Map < BaseBuilder
46
55
  self.partial_name = 'map'
47
56
  end
@@ -118,7 +127,7 @@ module AbAdmin
118
127
  end
119
128
 
120
129
  class BatchAction
121
- attr_reader :name, :options, :data, :title, :form
130
+ attr_reader :name, :options, :data, :form
122
131
 
123
132
  def initialize(name, options={}, &block)
124
133
  @name = name
@@ -126,10 +135,13 @@ module AbAdmin
126
135
  if options.has_key?(:form)
127
136
  @form = options[:form].is_a?(String) ? options[:form] : "##{name}_batch_form"
128
137
  end
129
- @title = options[:title] || I18n.t("admin.actions.batch_#{name}.link", default: name.to_s.humanize)
130
138
  @data = block_given? ? block : name.to_sym
131
139
  end
132
140
 
141
+ def title
142
+ options[:title] || I18n.t("admin.actions.#{name}.title", default: name.to_s.humanize)
143
+ end
144
+
133
145
  def confirm
134
146
  options[:confirm]
135
147
  end
@@ -176,8 +188,12 @@ module AbAdmin
176
188
  @data = block
177
189
  end
178
190
 
179
- def apply(relation, params)
180
- data.is_a?(Proc) ? data.call(relation, params) : relation.public_send(name)
191
+ def param_name
192
+ options[:as] || name
193
+ end
194
+
195
+ def apply(context, relation)
196
+ data.is_a?(Proc) ? data.call(context, relation) : relation.public_send(name)
181
197
  end
182
198
  end
183
199
  end
@@ -1,9 +1,4 @@
1
1
  class Array
2
- # TOREMOVE after ruby 2.7.0
3
- def tally
4
- each_with_object(Hash.new(0)) { |v, h| h[v] += 1 }
5
- end
6
-
7
2
  def deep_merge_hashes
8
3
  self.inject({}) do |res, h|
9
4
  raise Exception.new("Not a hash #{h}") unless h.is_a?(Hash)
@@ -1,11 +1,6 @@
1
1
  class String
2
2
  def no_html
3
- str = self.dup
4
- str.gsub!(/<br\/?>/, ' ')
5
- str.gsub!(/<\/?[^>]*>/, '')
6
- str.strip!
7
- str.gsub!('&nbsp;', ' ')
8
- str
3
+ self.dup.gsub(/<br\/?>/, ' ').gsub(/<\/?[^>]*>/, '').strip.gsub('&nbsp;', ' ').gsub('&gt;', '>').gsub('&lt;', '<')
9
4
  end
10
5
  end
11
6