avo 2.17.1.pre.2.customauthorizationclients → 2.17.1.pre.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of avo might be problematic. Click here for more details.

Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +6 -5
  3. data/app/components/avo/button_component.rb +2 -2
  4. data/app/components/avo/fields/common/single_file_viewer_component.html.erb +3 -3
  5. data/app/components/avo/fields/date_field/edit_component.html.erb +2 -0
  6. data/app/components/avo/fields/date_field/index_component.html.erb +1 -0
  7. data/app/components/avo/fields/date_field/show_component.html.erb +1 -0
  8. data/app/components/avo/fields/date_time_field/edit_component.html.erb +2 -0
  9. data/app/components/avo/fields/date_time_field/index_component.html.erb +1 -0
  10. data/app/components/avo/fields/date_time_field/show_component.html.erb +1 -0
  11. data/app/components/avo/fields/time_field/edit_component.html.erb +40 -0
  12. data/app/components/avo/fields/time_field/edit_component.rb +4 -0
  13. data/app/components/avo/fields/time_field/index_component.html.erb +15 -0
  14. data/app/components/avo/fields/time_field/index_component.rb +4 -0
  15. data/app/components/avo/fields/time_field/show_component.html.erb +15 -0
  16. data/app/components/avo/fields/time_field/show_component.rb +4 -0
  17. data/app/components/avo/index/resource_controls_component.html.erb +2 -2
  18. data/app/components/avo/panel_component.html.erb +2 -4
  19. data/app/components/avo/panel_component.rb +2 -4
  20. data/app/components/avo/sidebar_profile_component.html.erb +4 -1
  21. data/app/components/avo/tab_switcher_component.html.erb +1 -1
  22. data/app/components/avo/tab_switcher_component.rb +2 -0
  23. data/app/components/avo/views/resource_edit_component.html.erb +10 -12
  24. data/app/components/avo/views/resource_show_component.html.erb +21 -25
  25. data/app/controllers/avo/application_controller.rb +17 -9
  26. data/app/controllers/avo/associations_controller.rb +1 -1
  27. data/app/helpers/avo/application_helper.rb +4 -0
  28. data/app/javascript/js/controllers/fields/date_field_controller.js +61 -21
  29. data/app/javascript/js/controllers/search_controller.js +3 -0
  30. data/avo.gemspec +1 -1
  31. data/db/factories.rb +1 -0
  32. data/lib/avo/concerns/handles_field_args.rb +4 -0
  33. data/lib/avo/configuration.rb +0 -2
  34. data/lib/avo/fields/base_field.rb +5 -1
  35. data/lib/avo/fields/date_field.rb +2 -0
  36. data/lib/avo/fields/time_field.rb +55 -0
  37. data/lib/avo/services/authorization_service.rb +62 -43
  38. data/lib/avo/version.rb +1 -1
  39. data/lib/avo.rb +0 -4
  40. data/lib/generators/avo/templates/initializer/avo.tt +0 -1
  41. data/lib/generators/avo/templates/locales/avo.nb.yml +1 -1
  42. data/lib/generators/avo/templates/locales/avo.nn.yml +7 -7
  43. data/public/avo-assets/avo.base.css +12 -6
  44. data/public/avo-assets/avo.base.js +64 -64
  45. data/public/avo-assets/avo.base.js.map +2 -2
  46. metadata +12 -6
  47. data/lib/avo/services/authorization_clients/pundit_client.rb +0 -51
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e76409a6dd8d5c3a89a467e4cbed5e46f0c292988fd60efa5a1c673be10e36a
4
- data.tar.gz: '09bde2e9be219d3f9af5fb3256ee9ed60c77d86d587e8f9131c8d3e95d03ef29'
3
+ metadata.gz: e373d0b7b292a05ed70462e7a20e78a2f7c559a94f7aca774cd68165e18d2c70
4
+ data.tar.gz: 545d0fb57feac7577871bceeb107eec82a22d3f8ea0fe50370e93083b4fa4667
5
5
  SHA512:
6
- metadata.gz: 82ceba1946394ea1bea5cf4ef8283d75ddb40fdcfcfdd55ee19cad8aff34ed873698442dd846711134e6d961e288ca723cf8f9fab0aab122c0a49319e432ff05
7
- data.tar.gz: 3829a9ec1348318c537aee5954463c41864ee710790a8a333c2f075102d205ec90e50e70f766051aa2e892c2d0d8609b0bedc299a502d557a0806e9c34107269
6
+ metadata.gz: 7854ba8a53232541ed1ae751099ccf40a339bc25ab85f6cd2e3fa28b7d958f7e480e4d53acea0110acd7167e710e3fdfef91104d567a796b832360c2ce699a46
7
+ data.tar.gz: d00ec57468d98d63e4e594d93e1eb3bce3bc4734eb65951805124511d2e45eabab646b4b9f16e6d0f458c76732e7cf64d00bfa828154ea92173483bcad174afe
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (2.17.1.pre.2.customauthorizationclients)
4
+ avo (2.17.1.pre.3)
5
5
  active_link_to
6
6
  addressable
7
7
  breadcrumbs_on_rails
@@ -17,7 +17,7 @@ PATH
17
17
  pundit
18
18
  rails (>= 6.0)
19
19
  turbo-rails
20
- view_component (= 2.60)
20
+ view_component
21
21
  zeitwerk
22
22
 
23
23
  GEM
@@ -265,7 +265,7 @@ GEM
265
265
  net-protocol
266
266
  timeout
267
267
  nio4r (2.5.8)
268
- nokogiri (1.13.8)
268
+ nokogiri (1.13.9)
269
269
  mini_portile2 (~> 2.8.0)
270
270
  racc (~> 1.4)
271
271
  orm_adapter (0.5.0)
@@ -413,8 +413,9 @@ GEM
413
413
  tzinfo (2.0.5)
414
414
  concurrent-ruby (~> 1.0)
415
415
  unicode-display_width (2.2.0)
416
- view_component (2.60.0)
416
+ view_component (2.74.1)
417
417
  activesupport (>= 5.0.0, < 8.0)
418
+ concurrent-ruby (~> 1.0)
418
419
  method_source (~> 1.0)
419
420
  warden (1.2.9)
420
421
  rack (>= 2.0.9)
@@ -437,7 +438,7 @@ GEM
437
438
  websocket-extensions (0.1.5)
438
439
  xpath (3.2.0)
439
440
  nokogiri (~> 1.8)
440
- zeitwerk (2.6.0)
441
+ zeitwerk (2.6.1)
441
442
 
442
443
  PLATFORMS
443
444
  ruby
@@ -88,8 +88,8 @@ class Avo::ButtonComponent < ViewComponent::Base
88
88
  end
89
89
 
90
90
  def output_button
91
- if @args[:method].present?
92
- button_to @args[:url], **args do
91
+ if args.dig(:method).present? || args.dig(:data, :turbo_method).present?
92
+ button_to args[:url], **args do
93
93
  full_content
94
94
  end
95
95
  else
@@ -36,15 +36,15 @@
36
36
  <% if @resource.authorization.authorize_action(:delete_attachments?, raise_exception: false) %>
37
37
  <%= a_link destroy_path,
38
38
  icon: 'heroicons/outline/trash',
39
- method: :delete,
40
39
  color: :red,
41
40
  compact: true,
42
41
  size: :xs,
43
42
  class: 'text-center',
44
43
  title: t('avo.delete_file', item: file.filename),
45
44
  data: {
46
- 'turbo-frame': 'destroy_attachment_form',
47
- confirm: t('avo.are_you_sure'),
45
+ turbo_method: :delete,
46
+ turbo_frame: 'destroy_attachment_form',
47
+ turbo_confirm: t('avo.are_you_sure'),
48
48
  tippy: :tooltip
49
49
  } %>
50
50
  <% end %>
@@ -7,6 +7,8 @@
7
7
  date_field_picker_format_value: @field.picker_format,
8
8
  date_field_first_day_of_week_value: @field.first_day_of_week,
9
9
  date_field_disable_mobile_value: @field.disable_mobile,
10
+ date_field_field_type_value: "date",
11
+ date_field_picker_options_value: @field.picker_options,
10
12
  } do %>
11
13
  <%= datetime_field "fake_#{@field.id}", "fake",
12
14
  value: @field.edit_formatted_value,
@@ -3,6 +3,7 @@
3
3
  controller: "date-field",
4
4
  date_field_view_value: @view,
5
5
  date_field_format_value: @field.format,
6
+ date_field_field_type_value: "date",
6
7
  } do %>
7
8
  <%= @field.formatted_value %>
8
9
  <% end %>
@@ -3,6 +3,7 @@
3
3
  controller: "date-field",
4
4
  date_field_view_value: @view,
5
5
  date_field_format_value: @field.format,
6
+ date_field_field_type_value: "date",
6
7
  } do %>
7
8
  <%= @field.formatted_value %>
8
9
  <% end %>
@@ -8,6 +8,8 @@
8
8
  date_field_disable_mobile_value: @field.disable_mobile,
9
9
  date_field_time24_hr_value: @field.time_24hr,
10
10
  date_field_timezone_value: @field.timezone,
11
+ date_field_field_type_value: "dateTime",
12
+ date_field_picker_options_value: @field.picker_options,
11
13
  } do %>
12
14
  <%= datetime_field "fake_#{@field.id}", "fake",
13
15
  value: @field.edit_formatted_value,
@@ -6,6 +6,7 @@
6
6
  date_field_format_value: @field.format,
7
7
  date_field_timezone_value: @field.timezone,
8
8
  date_field_picker_format_value: @field.picker_format,
9
+ date_field_field_type_value: "dateTime",
9
10
  } do %>
10
11
  <%= @field.formatted_value %>
11
12
  <% end %>
@@ -6,6 +6,7 @@
6
6
  date_field_format_value: @field.format,
7
7
  date_field_timezone_value: @field.timezone,
8
8
  date_field_picker_format_value: @field.picker_format,
9
+ date_field_field_type_value: "dateTime",
9
10
  } do %>
10
11
  <%= @field.formatted_value %>
11
12
  <% end %>
@@ -0,0 +1,40 @@
1
+ <%= field_wrapper **field_wrapper_args do %>
2
+ <%= content_tag :div, data: {
3
+ controller: "date-field",
4
+ date_field_view_value: @view,
5
+ date_field_enable_time_value: true,
6
+ date_field_picker_format_value: @field.picker_format,
7
+ date_field_disable_mobile_value: @field.disable_mobile,
8
+ date_field_time24_hr_value: @field.time_24hr,
9
+ date_field_no_calendar_value: true,
10
+ date_field_timezone_value: @field.timezone,
11
+ date_field_relative_value: @field.relative,
12
+ date_field_field_type_value: "time",
13
+ date_field_picker_options_value: @field.picker_options,
14
+ } do %>
15
+ <%= datetime_field "fake_#{@field.id}", "fake",
16
+ value: @field.edit_formatted_value,
17
+ class: classes("w-full"),
18
+ data: {
19
+ 'date-field-target': 'fakeInput',
20
+ placeholder: @field.placeholder,
21
+ **@field.get_html(:data, view: view, element: :input)
22
+ },
23
+ disabled: @field.is_readonly?,
24
+ placeholder: @field.placeholder,
25
+ style: @field.get_html(:style, view: view, element: :input)
26
+ %>
27
+ <%= @form.text_field @field.id,
28
+ value: @field.edit_formatted_value,
29
+ class: classes("w-full hidden"),
30
+ data: {
31
+ 'date-field-target': 'input',
32
+ placeholder: @field.placeholder,
33
+ **@field.get_html(:data, view: view, element: :input)
34
+ },
35
+ disabled: @field.is_readonly?,
36
+ placeholder: @field.placeholder,
37
+ style: @field.get_html(:style, view: view, element: :input)
38
+ %>
39
+ <% end %>
40
+ <% end %>
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Fields::TimeField::EditComponent < Avo::Fields::EditComponent
4
+ end
@@ -0,0 +1,15 @@
1
+ <%= index_field_wrapper **field_wrapper_args do %>
2
+ <%= content_tag :div, data: {
3
+ controller: "date-field",
4
+ date_field_view_value: @view,
5
+ date_field_enable_time_value: true,
6
+ date_field_format_value: @field.format,
7
+ date_field_timezone_value: @field.timezone,
8
+ date_field_picker_format_value: @field.picker_format,
9
+ date_field_no_calendar_value: true,
10
+ date_field_relative_value: @field.relative,
11
+ date_field_field_type_value: "time",
12
+ } do %>
13
+ <%= @field.formatted_value %>
14
+ <% end %>
15
+ <% end %>
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Fields::TimeField::IndexComponent < Avo::Fields::IndexComponent
4
+ end
@@ -0,0 +1,15 @@
1
+ <%= field_wrapper **field_wrapper_args do %>
2
+ <%= content_tag :div, data: {
3
+ controller: "date-field",
4
+ date_field_view_value: @view,
5
+ date_field_enable_time_value: true,
6
+ date_field_format_value: @field.format,
7
+ date_field_timezone_value: @field.timezone,
8
+ date_field_picker_format_value: @field.picker_format,
9
+ date_field_no_calendar_value: true,
10
+ date_field_relative_value: @field.relative,
11
+ date_field_field_type_value: "time",
12
+ } do %>
13
+ <%= @field.formatted_value %>
14
+ <% end %>
15
+ <% end %>
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Fields::TimeField::ShowComponent < Avo::Fields::ShowComponent
4
+ end
@@ -42,7 +42,7 @@
42
42
  type: :submit,
43
43
  data: {
44
44
  target: 'control:detach',
45
- confirm: t('avo.are_you_sure_detach_item', item: singular_resource_name),
45
+ turbo_confirm: t('avo.are_you_sure_detach_item', item: singular_resource_name),
46
46
  control: :detach,
47
47
  'resource-id': @resource.model.id,
48
48
  'tippy': 'tooltip',
@@ -66,7 +66,7 @@
66
66
  type: :submit,
67
67
  data: {
68
68
  target: 'control:destroy',
69
- confirm: t('avo.are_you_sure', item: singular_resource_name),
69
+ turbo_confirm: t('avo.are_you_sure', item: singular_resource_name),
70
70
  control: :destroy,
71
71
  'resource-id': @resource.model.id,
72
72
  'tippy': 'tooltip',
@@ -25,10 +25,8 @@
25
25
  <% end %>
26
26
  <% if body? %>
27
27
  <div class="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:gap-4 w-full">
28
- <div class="flex-1 overflow-auto <% if sidebar? %> w-2/3 <% end %>">
29
- <div class="relative <%= white_panel_classes %> <%= @body_classes %>">
30
- <%= body %>
31
- </div>
28
+ <div class="relative flex-1 overflow-auto <%= white_panel_classes %> <%= @body_classes %> <% if sidebar? %> w-2/3 <% end %>">
29
+ <%= body %>
32
30
  </div>
33
31
  <% if sidebar? %>
34
32
  <div class="w-full sm:w-1/3 flex-shrink-0 h-full <%= white_panel_classes %>">
@@ -5,6 +5,8 @@ class Avo::PanelComponent < ViewComponent::Base
5
5
  attr_reader :name
6
6
  attr_reader :classes
7
7
 
8
+ delegate :white_panel_classes, to: :helpers
9
+
8
10
  renders_one :tools
9
11
  renders_one :body
10
12
  renders_one :sidebar
@@ -26,10 +28,6 @@ class Avo::PanelComponent < ViewComponent::Base
26
28
 
27
29
  private
28
30
 
29
- def white_panel_classes
30
- "bg-white rounded shadow"
31
- end
32
-
33
31
  def data_attributes
34
32
  @data.merge({"panel-index": @index})
35
33
  end
@@ -37,8 +37,11 @@
37
37
  <%# Example link below %>
38
38
  <%#= render Avo::ProfileItemComponent.new label: 'Profile', path: '/profile', icon: 'user-circle' %>
39
39
  <%= button_to helpers.main_app.send(destroy_user_session_path),
40
- method: :delete,
41
40
  form: { "data-turbo" => "false" },
41
+ method: :delete,
42
+ data: {
43
+ confirm: t('avo.are_you_sure')
44
+ },
42
45
  class: "flex-1 flex items-center justify-center bg-white text-left cursor-pointer text-red-600 font-semibold hover:bg-red-100 block px-4 py-1 w-full py-3 text-center rounded w-full",
43
46
  form_class: 'flex-1' do %>
44
47
  <%= helpers.svg 'logout', class: 'h-4 mr-1' %> <%= t('avo.sign_out') %>
@@ -20,7 +20,7 @@
20
20
  </div>
21
21
  </div>
22
22
  <% else %>
23
- <div class="flex flex-wrap gap-2 bg-white p-2" data-target="tab-switcher" data-style="pills">
23
+ <div class="flex flex-wrap gap-2 p-2 <%= white_panel_classes %>" data-target="tab-switcher" data-style="pills">
24
24
  <% visible_items.each do |tab| %>
25
25
  <%= a_link tab_path(tab),
26
26
  color: selected?(tab) ? :primary : :gray,
@@ -11,6 +11,8 @@ class Avo::TabSwitcherComponent < Avo::BaseComponent
11
11
  attr_reader :view
12
12
  attr_reader :style
13
13
 
14
+ delegate :white_panel_classes, to: :helpers
15
+
14
16
  def initialize(resource:, group:, current_tab:, active_tab_name:, view:, style:)
15
17
  @active_tab_name = active_tab_name
16
18
  @resource = resource
@@ -25,18 +25,16 @@
25
25
  <% end %>
26
26
  <% if can_see_the_destroy_button? %>
27
27
  <%= a_link destroy_path,
28
- method: :delete,
29
- local: true,
30
- style: :text,
31
- loading: true,
32
- confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
33
- color: :red,
34
- icon: 'trash',
35
- form_class: 'flex flex-col sm:flex-row sm:inline-flex',
36
- data: {
37
- control: :destroy,
38
- 'resource-id': @resource.model.id,
39
- } do %>
28
+ style: :text,
29
+ color: :red,
30
+ icon: 'trash',
31
+ form_class: 'flex flex-col sm:flex-row sm:inline-flex',
32
+ data: {
33
+ turbo_confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
34
+ turbo_method: :delete,
35
+ control: :destroy,
36
+ 'resource-id': @resource.model.id,
37
+ } do %>
40
38
  <%= t('avo.delete').capitalize %>
41
39
  <% end %>
42
40
  <% end %>
@@ -21,17 +21,15 @@
21
21
  <% end %>
22
22
  <% elsif control.delete_button? %>
23
23
  <% if can_see_the_destroy_button? %>
24
- <%= a_button url: helpers.resource_path(model: @resource.model, resource: @resource),
25
- method: :delete,
26
- local: true,
24
+ <%= a_link helpers.resource_path(model: @resource.model, resource: @resource),
27
25
  style: :text,
28
- loading: true,
29
- confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
30
26
  color: :red,
31
27
  icon: 'trash',
32
28
  form_class: 'flex flex-col sm:flex-row sm:inline-flex',
33
29
  title: control.title,
34
30
  data: {
31
+ turbo_confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
32
+ turbo_method: :delete,
35
33
  control: :destroy,
36
34
  tippy: control.title ? :tooltip : nil,
37
35
  'resource-id': @resource.model.id,
@@ -84,13 +82,13 @@
84
82
  <% elsif control.detach_button? %>
85
83
  <% if @reflection.present? && @resource.model.present? && can_detach? %>
86
84
  <%= a_button url: detach_path,
87
- icon: 'detach',
88
- method: :delete,
89
- form_class: 'flex flex-col sm:flex-row sm:inline-flex',
90
- style: :text,
91
- data: {
92
- confirm: "Are you sure you want to detach this #{title}."
93
- } do %>
85
+ icon: 'detach',
86
+ method: :delete,
87
+ form_class: 'flex flex-col sm:flex-row sm:inline-flex',
88
+ style: :text,
89
+ data: {
90
+ confirm: "Are you sure you want to detach this #{title}."
91
+ } do %>
94
92
  <%= control.label %>
95
93
  <% end %>
96
94
  <% end %>
@@ -126,19 +124,17 @@
126
124
  <%= t('avo.go_back') %>
127
125
  <% end %>
128
126
  <% if can_see_the_destroy_button? %>
129
- <%= a_button url: destroy_path,
130
- method: :delete,
131
- local: true,
132
- style: :text,
133
- loading: true,
134
- confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
135
- color: :red,
136
- icon: 'trash',
137
- form_class: 'flex flex-col sm:flex-row sm:inline-flex',
138
- data: {
139
- control: :destroy,
140
- 'resource-id': @resource.model.id,
141
- } do %>
127
+ <%= a_link destroy_path,
128
+ style: :text,
129
+ color: :red,
130
+ icon: 'trash',
131
+ form_class: 'flex flex-col sm:flex-row sm:inline-flex',
132
+ data: {
133
+ turbo_confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
134
+ turbo_method: :delete,
135
+ control: :destroy,
136
+ 'resource-id': @resource.model.id,
137
+ } do %>
142
138
  <%= t('avo.delete').capitalize %>
143
139
  <% end %>
144
140
  <% end %>
@@ -1,5 +1,11 @@
1
1
  module Avo
2
2
  class ApplicationController < ::ActionController::Base
3
+ if defined?(Pundit::Authorization)
4
+ include Pundit::Authorization
5
+ else
6
+ include Pundit
7
+ end
8
+
3
9
  include Pagy::Backend
4
10
  include Avo::ApplicationHelper
5
11
  include Avo::UrlHelpers
@@ -18,7 +24,7 @@ module Avo
18
24
  before_action :set_view
19
25
  before_action :set_sidebar_open
20
26
 
21
- rescue_from Avo::NotAuthorizedError, with: :render_unauthorized
27
+ rescue_from Pundit::NotAuthorizedError, with: :render_unauthorized
22
28
  rescue_from ActiveRecord::RecordInvalid, with: :exception_logger
23
29
 
24
30
  helper_method :_current_user, :resources_path, :resource_path, :new_resource_path, :edit_resource_path, :resource_attach_path, :resource_detach_path, :related_resources_path, :turbo_frame_request?, :resource_view_path
@@ -251,16 +257,18 @@ module Avo
251
257
  instance_eval(&Avo.configuration.authenticate)
252
258
  end
253
259
 
254
- def render_unauthorized(_exception)
255
- flash.now[:notice] = t "avo.not_authorized"
260
+ def render_unauthorized(exception)
261
+ if !exception.is_a? Pundit::NotDefinedError
262
+ flash.now[:notice] = t "avo.not_authorized"
256
263
 
257
- redirect_url = if request.referrer.blank? || (request.referrer == request.url)
258
- root_url
259
- else
260
- request.referrer
261
- end
264
+ redirect_url = if request.referrer.blank? || (request.referrer == request.url)
265
+ root_url
266
+ else
267
+ request.referrer
268
+ end
262
269
 
263
- redirect_to(redirect_url)
270
+ redirect_to(redirect_url)
271
+ end
264
272
  end
265
273
 
266
274
  def set_authorization
@@ -157,7 +157,7 @@ module Avo
157
157
  private
158
158
 
159
159
  def set_related_authorization
160
- @related_authorization = if related_resource
160
+ @authorization = if related_resource
161
161
  related_resource.authorization(user: _current_user)
162
162
  else
163
163
  Services::AuthorizationService.new _current_user
@@ -89,6 +89,10 @@ module Avo
89
89
  classes
90
90
  end
91
91
 
92
+ def white_panel_classes
93
+ "bg-white rounded shadow-md"
94
+ end
95
+
92
96
  def get_model_class(model)
93
97
  if model.instance_of?(Class)
94
98
  model
@@ -7,6 +7,9 @@ function universalTimestamp(timestampStr) {
7
7
  return new Date(new Date(timestampStr).getTime() + (new Date(timestampStr).getTimezoneOffset() * 60 * 1000))
8
8
  }
9
9
 
10
+ const RAW_DATE_FORMAT = 'y/LL/dd'
11
+ const RAW_TIME_FORMAT = 'TT'
12
+
10
13
  export default class extends Controller {
11
14
  static targets = ['input', 'fakeInput']
12
15
 
@@ -19,6 +22,10 @@ export default class extends Controller {
19
22
  firstDayOfWeek: Number,
20
23
  time24Hr: Boolean,
21
24
  disableMobile: Boolean,
25
+ noCalendar: Boolean,
26
+ relative: Boolean,
27
+ fieldType: { type: String, default: 'dateTime' },
28
+ pickerOptions: { type: Object, default: {} },
22
29
  }
23
30
 
24
31
  flatpickrInstance;
@@ -90,8 +97,8 @@ export default class extends Controller {
90
97
  initShow() {
91
98
  let value = this.parsedValue
92
99
 
93
- // Set the zone only if the type of field is date time.
94
- if (this.enableTimeValue) {
100
+ // Set the zone only if the type of field is date time or relative time.
101
+ if (this.enableTimeValue && this.relativeValue) {
95
102
  value = value.setZone(this.displayTimezone)
96
103
  }
97
104
 
@@ -109,6 +116,8 @@ export default class extends Controller {
109
116
  },
110
117
  altInput: true,
111
118
  onChange: this.onChange.bind(this),
119
+ noCalendar: false,
120
+ ...this.pickerOptionsValue,
112
121
  }
113
122
 
114
123
  // Set the format of the displayed input field.
@@ -124,24 +133,45 @@ export default class extends Controller {
124
133
  options.enableTime = this.enableTimeValue
125
134
  options.enableSeconds = this.enableTimeValue
126
135
 
127
- // enable timezone display
128
- if (this.enableTimeValue) {
129
- options.defaultDate = this.parsedValue.setZone(this.displayTimezone).toISO()
136
+ // Hide calendar and only keep time picker.
137
+ options.noCalendar = this.noCalendarValue
130
138
 
131
- options.dateFormat = 'Y-m-d H:i:S'
132
- } else {
133
- // Because the browser treats the date like a timestamp and updates it at 00:00 hour, when on a western timezone the date will be converted with one day offset.
134
- // Ex: 2022-01-30 will render as 2022-01-29 on an American timezone
135
- options.defaultDate = universalTimestamp(this.initialValue)
139
+ if (this.initialValue) {
140
+ // Enable timezone display
141
+ if (this.enableTimeValue && this.relativeValue) {
142
+ options.defaultDate = this.parsedValue.setZone(this.displayTimezone).toISO()
143
+
144
+ options.dateFormat = 'Y-m-d H:i:S'
145
+ } else {
146
+ // Because the browser treats the date like a timestamp and updates it at 00:00 hour, when on a western timezone the date will be converted with one day offset.
147
+ // Ex: 2022-01-30 will render as 2022-01-29 on an American timezone
148
+ options.defaultDate = universalTimestamp(this.initialValue)
149
+ }
136
150
  }
137
151
 
138
152
  this.flatpickrInstance = flatpickr(this.fakeInputTarget, options)
139
153
 
140
- if (this.enableTimeValue) {
141
- this.updateRealInput(this.parsedValue.setZone(this.displayTimezone).toISO())
142
- } else {
143
- this.updateRealInput(universalTimestamp(this.initialValue))
154
+ // Don't try to parse the value if the input is empty.
155
+ if (!this.initialValue) {
156
+ return
144
157
  }
158
+
159
+ let value
160
+ switch (this.fieldTypeValue) {
161
+ case 'time':
162
+ // For time values, we should maintain the real value and format it to a time-friendly format.
163
+ value = this.parsedValue.setZone(this.displayTimezone, { keepLocalTime: true }).toFormat(RAW_TIME_FORMAT)
164
+ break
165
+ case 'date':
166
+ value = DateTime.fromJSDate(universalTimestamp(this.initialValue)).toFormat(RAW_DATE_FORMAT)
167
+ break
168
+ default:
169
+ case 'dateTime':
170
+ value = this.parsedValue.setZone(this.displayTimezone).toISO()
171
+ break
172
+ }
173
+
174
+ this.updateRealInput(value)
145
175
  }
146
176
 
147
177
  onChange(selectedDates) {
@@ -152,24 +182,34 @@ export default class extends Controller {
152
182
  return
153
183
  }
154
184
 
155
- let time
156
185
  let args = {}
157
186
 
158
- if (this.timezoneValue) {
187
+ // For values that involve time we should keep the local time.
188
+ if (this.timezoneValue || !this.relativeValue) {
159
189
  args = { keepLocalTime: true }
160
190
  } else {
161
191
  args = { keepLocalTime: false }
162
192
  }
163
193
 
164
- if (this.enableTimeValue) {
165
- time = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', args)
166
- } else {
167
- time = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', { keepLocalTime: true })
194
+ let value
195
+ switch (this.fieldTypeValue) {
196
+ case 'time':
197
+ // For time values, we should maintain the real value and format it to a time-friendly format.
198
+ value = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', args).toFormat(RAW_TIME_FORMAT)
199
+ break
200
+ case 'date':
201
+ value = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', { keepLocalTime: true }).toFormat(RAW_DATE_FORMAT)
202
+ break
203
+ default:
204
+ case 'dateTime':
205
+ value = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', args).toISO()
206
+ break
168
207
  }
169
208
 
170
- this.updateRealInput(time)
209
+ this.updateRealInput(value)
171
210
  }
172
211
 
212
+ // Value should be a string
173
213
  updateRealInput(value) {
174
214
  this.inputTarget.value = value
175
215
  }
@@ -73,6 +73,9 @@ export default class extends Controller {
73
73
  Mousetrap.bind(['command+k', 'ctrl+k'], () => this.showSearchPanel())
74
74
  }
75
75
 
76
+ // This line fixes a bug where the search box would be duplicated on back navigation.
77
+ this.autocompleteTarget.innerHTML = ''
78
+
76
79
  autocomplete({
77
80
  container: this.autocompleteTarget,
78
81
  placeholder: this.translationKeys.placeholder,