tiny_admin 0.10.0 → 0.11.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/tiny_admin/actions/index.rb +4 -4
  4. data/lib/tiny_admin/authentication.rb +8 -8
  5. data/lib/tiny_admin/basic_app.rb +5 -3
  6. data/lib/tiny_admin/field.rb +5 -4
  7. data/lib/tiny_admin/plugins/active_record_repository.rb +1 -1
  8. data/lib/tiny_admin/plugins/base_repository.rb +2 -1
  9. data/lib/tiny_admin/plugins/no_auth.rb +2 -2
  10. data/lib/tiny_admin/plugins/simple_auth.rb +10 -10
  11. data/lib/tiny_admin/raw_html.rb +11 -0
  12. data/lib/tiny_admin/router.rb +36 -29
  13. data/lib/tiny_admin/settings.rb +22 -8
  14. data/lib/tiny_admin/store.rb +9 -12
  15. data/lib/tiny_admin/support.rb +14 -2
  16. data/lib/tiny_admin/utils.rb +7 -5
  17. data/lib/tiny_admin/version.rb +1 -1
  18. data/lib/tiny_admin/views/actions/index.rb +23 -27
  19. data/lib/tiny_admin/views/actions/show.rb +15 -21
  20. data/lib/tiny_admin/views/attributes.rb +18 -0
  21. data/lib/tiny_admin/views/basic_layout.rb +1 -6
  22. data/lib/tiny_admin/views/components/actions_buttons.rb +24 -0
  23. data/lib/tiny_admin/views/components/basic_component.rb +1 -5
  24. data/lib/tiny_admin/views/components/field_value.rb +15 -4
  25. data/lib/tiny_admin/views/components/filters_form.rb +21 -21
  26. data/lib/tiny_admin/views/components/flash.rb +5 -5
  27. data/lib/tiny_admin/views/components/head.rb +4 -4
  28. data/lib/tiny_admin/views/components/navbar.rb +17 -17
  29. data/lib/tiny_admin/views/components/pagination.rb +15 -15
  30. data/lib/tiny_admin/views/components/widgets.rb +23 -8
  31. data/lib/tiny_admin/views/default_layout.rb +8 -8
  32. data/lib/tiny_admin/views/pages/content.rb +4 -4
  33. data/lib/tiny_admin/views/pages/error_page.rb +26 -0
  34. data/lib/tiny_admin/views/pages/page_not_allowed.rb +2 -10
  35. data/lib/tiny_admin/views/pages/page_not_found.rb +2 -10
  36. data/lib/tiny_admin/views/pages/record_not_found.rb +2 -10
  37. data/lib/tiny_admin/views/pages/root.rb +3 -3
  38. data/lib/tiny_admin/views/pages/simple_auth_login.rb +12 -12
  39. data/lib/tiny_admin.rb +8 -8
  40. metadata +13 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 748ac60bbfef74485795f95728033866510e728572d391b2a3f381fde88434f6
4
- data.tar.gz: 59c0a78183a960e28cb1670d4484be18e368ec48cf622bd8034087192385b7dd
3
+ metadata.gz: 6b1b97d53e29a9534e5bad33db4ab61e52de4325cd68d6556da668bf940815b5
4
+ data.tar.gz: 2d94e52253fdf7fe44edf2ef731c166ade81c5972b84fac80d3adc99d7e347d2
5
5
  SHA512:
6
- metadata.gz: 8ba6531f80fbaab695c59a309e7249f071a1f4491fd7800edb2562571beb352d3f4faff625876cf6dc87e9cdcbf8d12d002f343076446c8ad82653c8d488ce78
7
- data.tar.gz: 162637202396ce2106c9ad9c2117a22becc3ad99c3a4e0f72f850624089cb110e2eda4ae39aa66e6c4897bb6c5ad0789003d10711c94ba74f28616afa3e5e82a
6
+ metadata.gz: b681c29bc30097703aa541af928a9f0d73e698cb0a7bf1e95264f92be92c6b420f556f1eda1fe9dd3c6a12efcefa3c975c84ab52664607cf2a9eab15e88e3192
7
+ data.tar.gz: dd1ae44d4d8eb14d212c980487ba3f57a37d0cf460f7a6cfa05eced7436ec2b6bd5015fb822f565f7923c8110e81c5c7a6072f688cb7b637a5b33479e16db668
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/tiny_admin.svg)](https://badge.fury.io/rb/tiny_admin)
4
4
  [![Gem Downloads](https://badgen.net/rubygems/dt/tiny_admin)](https://rubygems.org/gems/tiny_admin)
5
5
  [![Linters](https://github.com/blocknotes/tiny_admin/actions/workflows/linters.yml/badge.svg)](https://github.com/blocknotes/tiny_admin/actions/workflows/linters.yml)
6
- [![Specs](https://github.com/blocknotes/tiny_admin/actions/workflows/specs.yml/badge.svg)](https://github.com/blocknotes/tiny_admin/actions/workflows/specs.yml)
6
+ [![Specs](https://github.com/blocknotes/tiny_admin/actions/workflows/tests.yml/badge.svg)](https://github.com/blocknotes/tiny_admin/actions/workflows/tests.yml)
7
7
 
8
8
  A compact and composable dashboard component for Ruby.
9
9
 
@@ -45,14 +45,14 @@ module TinyAdmin
45
45
  @params = context.request.params
46
46
  @repository = context.repository
47
47
  @pagination = options[:pagination] || 10
48
- @current_page = (params['p'] || 1).to_i
49
- @query_string = params_to_s(params.except('p'))
48
+ @current_page = (params["p"] || 1).to_i
49
+ @query_string = params_to_s(params.except("p"))
50
50
  end
51
51
 
52
52
  def prepare_filters(fields)
53
53
  filters = (options[:filters] || []).map { _1.is_a?(Hash) ? _1 : { field: _1 } }
54
- filters = filters.each_with_object({}) { |filter, result| result[filter[:field]] = filter }
55
- values = (params['q'] || {})
54
+ filters = filters.to_h { |filter| [filter[:field], filter] }
55
+ values = params["q"] || {}
56
56
  fields.each_with_object({}) do |(name, field), result|
57
57
  result[field] = { value: values[name], filter: filters[name] } if filters.key?(name)
58
58
  end
@@ -3,7 +3,7 @@
3
3
  module TinyAdmin
4
4
  class Authentication < BasicApp
5
5
  route do |r|
6
- r.get 'unauthenticated' do
6
+ r.get "unauthenticated" do
7
7
  if current_user
8
8
  r.redirect TinyAdmin.settings.root_path
9
9
  else
@@ -11,15 +11,15 @@ module TinyAdmin
11
11
  end
12
12
  end
13
13
 
14
- r.post 'unauthenticated' do
14
+ r.post "unauthenticated" do
15
15
  warning = TinyAdmin.settings.helper_class.label_for(
16
- 'Failed to authenticate',
17
- options: ['authentication.unauthenticated']
16
+ "Failed to authenticate",
17
+ options: ["authentication.unauthenticated"]
18
18
  )
19
19
  render_login(warnings: [warning])
20
20
  end
21
21
 
22
- r.get 'logout' do
22
+ r.get "logout" do
23
23
  logout_user
24
24
  r.redirect TinyAdmin.settings.root_path
25
25
  end
@@ -33,9 +33,9 @@ module TinyAdmin
33
33
 
34
34
  page = prepare_page(login, options: %i[no_menu compact_layout])
35
35
  page.messages = {
36
- notices: notices || flash['notices'],
37
- warnings: warnings || flash['warnings'],
38
- errors: errors || flash['errors']
36
+ notices: notices || flash["notices"],
37
+ warnings: warnings || flash["warnings"],
38
+ errors: errors || flash["errors"]
39
39
  }
40
40
  render(inline: page.call)
41
41
  end
@@ -5,17 +5,19 @@ module TinyAdmin
5
5
  include Utils
6
6
 
7
7
  class << self
8
+ include Utils
9
+
8
10
  def authentication_plugin
9
11
  plugin = TinyAdmin.settings.authentication&.dig(:plugin)
10
- plugin_class = plugin.is_a?(String) ? Object.const_get(plugin) : plugin
12
+ plugin_class = to_class(plugin) if plugin
11
13
  plugin_class || TinyAdmin::Plugins::NoAuth
12
14
  end
13
15
  end
14
16
 
15
17
  plugin :flash
16
18
  plugin :not_found
17
- plugin :render, engine: 'html'
18
- plugin :sessions, secret: SecureRandom.hex(64)
19
+ plugin :render, engine: "html"
20
+ plugin :sessions, secret: ENV.fetch("TINY_ADMIN_SECRET") { SecureRandom.hex(64) }
19
21
 
20
22
  plugin authentication_plugin, TinyAdmin.settings.authentication
21
23
 
@@ -12,13 +12,13 @@ module TinyAdmin
12
12
  end
13
13
 
14
14
  def apply_call_option(target)
15
- messages = (options[:call] || '').split(',').map(&:strip)
15
+ messages = (options[:call] || "").split(",").map(&:strip)
16
16
  messages.inject(target) { |result, msg| result&.send(msg) } if messages.any?
17
17
  end
18
18
 
19
19
  def translate_value(value)
20
20
  if options && options[:method]
21
- method, *args = options[:method].split(',').map(&:strip)
21
+ method, *args = options[:method].split(",").map(&:strip)
22
22
  if options[:converter]
23
23
  Object.const_get(options[:converter]).send(method, value, options: args || [])
24
24
  else
@@ -30,10 +30,11 @@ module TinyAdmin
30
30
  end
31
31
 
32
32
  class << self
33
+ include Utils
34
+
33
35
  def create_field(name:, title: nil, type: nil, options: {})
34
36
  field_name = name.to_s
35
- field_title = field_name.respond_to?(:humanize) ? field_name.humanize : field_name.tr('_', ' ').capitalize
36
- new(name: field_name, title: title || field_title, type: type || :string, options: options || {})
37
+ new(name: field_name, title: title || humanize(field_name), type: type || :string, options: options || {})
37
38
  end
38
39
  end
39
40
  end
@@ -54,7 +54,7 @@ module TinyAdmin
54
54
  def apply_filters(query, filters)
55
55
  filters.each do |field, filter|
56
56
  value = filter&.dig(:value)
57
- next if value.nil? || value == ''
57
+ next if value.nil? || value == ""
58
58
 
59
59
  query =
60
60
  case field.type
@@ -3,7 +3,8 @@
3
3
  module TinyAdmin
4
4
  module Plugins
5
5
  class BaseRepository
6
- RecordNotFound = Class.new(StandardError)
6
+ class RecordNotFound < StandardError
7
+ end
7
8
 
8
9
  attr_reader :model
9
10
 
@@ -8,12 +8,12 @@ module TinyAdmin
8
8
  end
9
9
 
10
10
  module InstanceMethods
11
- def authenticate_user!
11
+ def authenticate_user! # rubocop:disable Naming/PredicateMethod
12
12
  true
13
13
  end
14
14
 
15
15
  def current_user
16
- 'admin'
16
+ "admin"
17
17
  end
18
18
 
19
19
  def logout_user
@@ -1,21 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'digest'
3
+ require "digest"
4
4
 
5
5
  module TinyAdmin
6
6
  module Plugins
7
7
  module SimpleAuth
8
8
  class << self
9
9
  def configure(app, opts = {})
10
- @@opts = opts || {} # rubocop:disable Style/ClassVars
11
- @@opts[:password] ||= ENV.fetch('ADMIN_PASSWORD_HASH', nil) # NOTE: fallback value
10
+ opts ||= {}
11
+ password_hash = opts[:password] || ENV.fetch("ADMIN_PASSWORD_HASH", nil)
12
12
 
13
13
  Warden::Strategies.add(:secret) do
14
- def authenticate!
15
- secret = params['secret'] || ''
16
- return fail(:invalid_credentials) if Digest::SHA512.hexdigest(secret) != @@opts[:password]
14
+ define_method(:authenticate!) do
15
+ secret = params["secret"] || ""
16
+ return fail(:invalid_credentials) if Digest::SHA512.hexdigest(secret) != password_hash
17
17
 
18
- success!(app: 'TinyAdmin')
18
+ success!(app: "TinyAdmin")
19
19
  end
20
20
  end
21
21
 
@@ -29,15 +29,15 @@ module TinyAdmin
29
29
 
30
30
  module InstanceMethods
31
31
  def authenticate_user!
32
- env['warden'].authenticate!
32
+ env["warden"].authenticate!
33
33
  end
34
34
 
35
35
  def current_user
36
- env['warden'].user
36
+ env["warden"].user
37
37
  end
38
38
 
39
39
  def logout_user
40
- env['warden'].logout
40
+ env["warden"].logout
41
41
  end
42
42
  end
43
43
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TinyAdmin
4
+ class RawHtml
5
+ attr_reader :to_s
6
+
7
+ def initialize(value)
8
+ @to_s = value.to_s
9
+ end
10
+ end
11
+ end
@@ -9,7 +9,7 @@ module TinyAdmin
9
9
  route do |r|
10
10
  TinyAdmin.settings.load_settings
11
11
 
12
- r.on 'auth' do
12
+ r.on "auth" do
13
13
  r.run Authentication
14
14
  end
15
15
 
@@ -25,7 +25,7 @@ module TinyAdmin
25
25
  # :nocov:
26
26
  end
27
27
 
28
- r.post '' do
28
+ r.post "" do
29
29
  r.redirect TinyAdmin.settings.root_path
30
30
  end
31
31
 
@@ -48,13 +48,13 @@ module TinyAdmin
48
48
 
49
49
  def render_page(page)
50
50
  if page.respond_to?(:messages=)
51
- page.messages = { notices: flash['notices'], warnings: flash['warnings'], errors: flash['errors'] }
51
+ page.messages = { notices: flash["notices"], warnings: flash["warnings"], errors: flash["errors"] }
52
52
  end
53
53
  render(inline: page.call)
54
54
  end
55
55
 
56
56
  def root_route(req)
57
- if authorization.allowed?(current_user, :root)
57
+ authorize!(:root) do
58
58
  if TinyAdmin.settings.root[:redirect]
59
59
  req.redirect route_for(TinyAdmin.settings.root[:redirect])
60
60
  else
@@ -62,31 +62,27 @@ module TinyAdmin
62
62
  attributes = TinyAdmin.settings.root.slice(:content, :title, :widgets)
63
63
  render_page prepare_page(page_class, attributes: attributes, params: request.params)
64
64
  end
65
- else
66
- render_page prepare_page(TinyAdmin.settings.page_not_allowed)
67
65
  end
68
66
  end
69
67
 
70
68
  def setup_page_route(req, slug, page_data)
71
69
  req.get slug do
72
- if authorization.allowed?(current_user, :page, slug)
70
+ authorize!(:page, slug) do
73
71
  attributes = page_data.slice(:content, :title, :widgets)
74
72
  render_page prepare_page(page_data[:class], slug: slug, attributes: attributes, params: request.params)
75
- else
76
- render_page prepare_page(TinyAdmin.settings.page_not_allowed)
77
73
  end
78
74
  end
79
75
  end
80
76
 
81
77
  def setup_resource_routes(req, slug, options:)
82
78
  req.on slug do
83
- setup_collection_routes(req, slug, options: options)
84
- setup_member_routes(req, slug, options: options)
79
+ repository = options[:repository].new(options[:model])
80
+ setup_collection_routes(req, slug, options: options, repository: repository)
81
+ setup_member_routes(req, slug, options: options, repository: repository)
85
82
  end
86
83
  end
87
84
 
88
- def setup_collection_routes(req, slug, options:)
89
- repository = options[:repository].new(options[:model])
85
+ def setup_collection_routes(req, slug, options:, repository:)
90
86
  action_options = options[:index] || {}
91
87
 
92
88
  # Custom actions
@@ -99,9 +95,9 @@ module TinyAdmin
99
95
  )
100
96
 
101
97
  # Index
102
- if options[:only].include?(:index) || options[:only].include?('index')
98
+ if options[:only].include?(:index) || options[:only].include?("index")
103
99
  req.is do
104
- if authorization.allowed?(current_user, :resource_index, slug)
100
+ authorize!(:resource_index, slug) do
105
101
  context = Context.new(
106
102
  actions: custom_actions,
107
103
  repository: repository,
@@ -111,15 +107,12 @@ module TinyAdmin
111
107
  )
112
108
  index_action = TinyAdmin::Actions::Index.new
113
109
  render_page index_action.call(app: self, context: context, options: action_options)
114
- else
115
- render_page prepare_page(TinyAdmin.settings.page_not_allowed)
116
110
  end
117
111
  end
118
112
  end
119
113
  end
120
114
 
121
- def setup_member_routes(req, slug, options:)
122
- repository = options[:repository].new(options[:model])
115
+ def setup_member_routes(req, slug, options:, repository:)
123
116
  action_options = (options[:show] || {}).merge(record_not_found_page: TinyAdmin.settings.record_not_found)
124
117
 
125
118
  req.on String do |reference|
@@ -134,9 +127,9 @@ module TinyAdmin
134
127
  )
135
128
 
136
129
  # Show
137
- if options[:only].include?(:show) || options[:only].include?('show')
130
+ if options[:only].include?(:show) || options[:only].include?("show")
138
131
  req.is do
139
- if authorization.allowed?(current_user, :resource_show, slug)
132
+ authorize!(:resource_show, slug) do
140
133
  context = Context.new(
141
134
  actions: custom_actions,
142
135
  reference: reference,
@@ -147,8 +140,6 @@ module TinyAdmin
147
140
  )
148
141
  show_action = TinyAdmin::Actions::Show.new
149
142
  render_page show_action.call(app: self, context: context, options: action_options)
150
- else
151
- render_page prepare_page(TinyAdmin.settings.page_not_allowed)
152
143
  end
153
144
  end
154
145
  end
@@ -157,11 +148,11 @@ module TinyAdmin
157
148
 
158
149
  def setup_custom_actions(req, custom_actions = nil, options:, repository:, slug:, reference: nil)
159
150
  (custom_actions || []).each_with_object({}) do |custom_action, result|
160
- action_slug, action = custom_action.first
161
- action_class = to_class(action)
151
+ action_slug, action_config = custom_action.first
152
+ action_class, http_method = parse_action_config(action_config)
162
153
 
163
- req.get action_slug.to_s do
164
- if authorization.allowed?(current_user, :custom_action, action_slug.to_s)
154
+ req.public_send(http_method, action_slug.to_s) do
155
+ authorize!(:custom_action, action_slug.to_s) do
165
156
  context = Context.new(
166
157
  actions: {},
167
158
  reference: reference,
@@ -172,8 +163,6 @@ module TinyAdmin
172
163
  )
173
164
  custom_action = action_class.new
174
165
  render_page custom_action.call(app: self, context: context, options: options)
175
- else
176
- render_page prepare_page(TinyAdmin.settings.page_not_allowed)
177
166
  end
178
167
  end
179
168
 
@@ -181,8 +170,26 @@ module TinyAdmin
181
170
  end
182
171
  end
183
172
 
173
+ def parse_action_config(config)
174
+ if config.is_a?(Hash)
175
+ action_class = to_class(config[:action] || config["action"])
176
+ http_method = (config[:method] || config["method"] || "get").to_sym
177
+ [action_class, http_method]
178
+ else
179
+ [to_class(config), :get]
180
+ end
181
+ end
182
+
184
183
  def authorization
185
184
  TinyAdmin.settings.authorization_class
186
185
  end
186
+
187
+ def authorize!(action, param = nil)
188
+ if authorization.allowed?(current_user, action, param)
189
+ yield
190
+ else
191
+ render_page prepare_page(TinyAdmin.settings.page_not_allowed)
192
+ end
193
+ end
187
194
  end
188
195
  end
@@ -19,9 +19,9 @@ module TinyAdmin
19
19
  %i[page_not_found] => Views::Pages::PageNotFound,
20
20
  %i[record_not_found] => Views::Pages::RecordNotFound,
21
21
  %i[repository] => Plugins::ActiveRecordRepository,
22
- %i[root_path] => '/admin',
22
+ %i[root_path] => "/admin",
23
23
  %i[root page] => Views::Pages::Root,
24
- %i[root title] => 'TinyAdmin',
24
+ %i[root title] => "TinyAdmin",
25
25
  %i[sections] => []
26
26
  }.freeze
27
27
 
@@ -67,6 +67,8 @@ module TinyAdmin
67
67
  end
68
68
 
69
69
  def load_settings
70
+ return if @loaded
71
+
70
72
  # default values
71
73
  DEFAULTS.each do |(option, param), default|
72
74
  if param
@@ -78,17 +80,23 @@ module TinyAdmin
78
80
  end
79
81
 
80
82
  @store ||= TinyAdmin::Store.new(self)
81
- self.root_path = '/' if root_path == ''
83
+ self.root_path = "/" if root_path == ""
82
84
 
83
- if authentication[:plugin] <= Plugins::SimpleAuth
85
+ if authentication[:plugin].is_a?(Module) && authentication[:plugin] <= Plugins::SimpleAuth
84
86
  logout_path = "#{root_path}/auth/logout"
85
- authentication[:logout] ||= TinyAdmin::Section.new(name: 'logout', slug: 'logout', path: logout_path)
87
+ authentication[:logout] ||= TinyAdmin::Section.new(name: "logout", slug: "logout", path: logout_path)
86
88
  end
87
89
  store.prepare_sections(sections, logout: authentication[:logout])
90
+ @loaded = true
88
91
  end
89
92
 
90
93
  def reset!
91
- @options = {}
94
+ @options = {
95
+ components: {},
96
+ sections: []
97
+ }
98
+ @store = nil
99
+ @loaded = false
92
100
  end
93
101
 
94
102
  private
@@ -104,12 +112,18 @@ module TinyAdmin
104
112
  value.each_key do |key2|
105
113
  path = [key, key2]
106
114
  if (DEFAULTS[path].is_a?(Class) || DEFAULTS[path].is_a?(Module)) && self[key][key2].is_a?(String)
107
- self[key][key2] = Object.const_get(self[key][key2])
115
+ self[key][key2] = resolve_class(self[key][key2], setting: "#{key}.#{key2}")
108
116
  end
109
117
  end
110
118
  elsif value.is_a?(String) && (DEFAULTS[[key]].is_a?(Class) || DEFAULTS[[key]].is_a?(Module))
111
- self[key] = Object.const_get(self[key])
119
+ self[key] = resolve_class(self[key], setting: key.to_s)
112
120
  end
113
121
  end
122
+
123
+ def resolve_class(class_name, setting:)
124
+ Object.const_get(class_name)
125
+ rescue NameError => e
126
+ raise NameError, "TinyAdmin: invalid class '#{class_name}' for setting '#{setting}' - #{e.message}"
127
+ end
114
128
  end
115
129
  end
@@ -22,18 +22,15 @@ module TinyAdmin
22
22
  end
23
23
 
24
24
  slug = section[:slug].to_s
25
- case section[:type]&.to_sym
26
- when :content
27
- list << add_content_section(slug, section)
28
- when :page
29
- list << add_page_section(slug, section)
30
- when :resource
31
- list << add_resource_section(slug, section)
32
- when :url
33
- list << add_url_section(slug, section)
34
- end
25
+ item = case section[:type]&.to_sym
26
+ when :content then add_content_section(slug, section)
27
+ when :page then add_page_section(slug, section)
28
+ when :resource then add_resource_section(slug, section)
29
+ when :url then add_url_section(slug, section)
30
+ end
31
+ list << item if item
35
32
  end
36
- navbar << logout if logout
33
+ @navbar << logout if logout
37
34
  end
38
35
 
39
36
  private
@@ -56,7 +53,7 @@ module TinyAdmin
56
53
  repository: to_class(section[:repository] || settings.repository)
57
54
  )
58
55
 
59
- hidden = section[:options] && (section[:options].include?(:hidden) || section[:options].include?('hidden'))
56
+ hidden = section[:options] && (section[:options].include?(:hidden) || section[:options].include?("hidden"))
60
57
  TinyAdmin::Section.new(name: section[:name], slug: slug) unless hidden
61
58
  end
62
59
 
@@ -3,6 +3,10 @@
3
3
  module TinyAdmin
4
4
  class Support
5
5
  class << self
6
+ def raw_html(value)
7
+ TinyAdmin::RawHtml.new(value)
8
+ end
9
+
6
10
  def call(value, options: [])
7
11
  options.inject(value) { |result, message| result&.send(message) } if value && options&.any?
8
12
  end
@@ -24,11 +28,19 @@ module TinyAdmin
24
28
  end
25
29
 
26
30
  def strftime(value, options: [])
27
- value&.strftime(options&.first || '%Y-%m-%d %H:%M')
31
+ value&.strftime(options&.first || "%Y-%m-%d %H:%M")
28
32
  end
29
33
 
30
34
  def to_date(value, options: [])
31
- value.to_date.to_s if value
35
+ value&.to_date&.to_s
36
+ rescue NoMethodError, ArgumentError
37
+ value&.to_s
38
+ end
39
+
40
+ def multiline(array, options: [])
41
+ return unless array.is_a?(Array)
42
+
43
+ raw_html(array.join("<br/>"))
32
44
  end
33
45
 
34
46
  def upcase(value, options: [])
@@ -1,17 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cgi"
4
+
3
5
  module TinyAdmin
4
6
  module Utils
5
7
  def params_to_s(params)
6
8
  list = params.each_with_object([]) do |(param, value), result|
7
9
  if value.is_a?(Hash)
8
- values = value.map { |key, val| "#{param}[#{key}]=#{val}" }
10
+ values = value.map { |key, val| "#{CGI.escape(param.to_s)}[#{CGI.escape(key.to_s)}]=#{CGI.escape(val.to_s)}" }
9
11
  result.concat(values)
10
12
  else
11
- result.push(["#{param}=#{value}"])
13
+ result.push("#{CGI.escape(param.to_s)}=#{CGI.escape(value.to_s)}")
12
14
  end
13
15
  end
14
- list.join('&')
16
+ list.join("&")
15
17
  end
16
18
 
17
19
  def prepare_page(page_class, slug: nil, attributes: nil, options: nil, params: nil)
@@ -40,9 +42,9 @@ module TinyAdmin
40
42
  end
41
43
 
42
44
  def humanize(string)
43
- return '' unless string
45
+ return "" unless string
44
46
 
45
- string.respond_to?(:humanize) ? string.humanize : string.tr('_', ' ').capitalize
47
+ string.respond_to?(:humanize) ? string.humanize : string.tr("_", " ").capitalize
46
48
  end
47
49
  end
48
50
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TinyAdmin
4
- VERSION = '0.10.0'
4
+ VERSION = "0.11.0"
5
5
  end