avo 4.0.8 → 4.0.10

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.
@@ -23,7 +23,7 @@
23
23
  }
24
24
 
25
25
  .filters-section__title {
26
- @apply text-sm font-medium text-foreground;
26
+ @apply flex items-center gap-1 text-sm font-medium text-foreground;
27
27
  }
28
28
 
29
29
  .filters-section__body {
@@ -22,7 +22,20 @@
22
22
  <div class="filters-card__content">
23
23
  <div class="divide-y divide-secondary">
24
24
  <% @filters.each do |filter| %>
25
- <%= render partial: filter.class.template, locals: {filter: filter} %>
25
+ <div class="filters-section">
26
+ <div class="filters-section__title">
27
+ <%= filter.name %>
28
+ <% if Rails.env.development? %>
29
+ <%= render Avo::DiscreetInformationComponent.new(
30
+ as: :icon,
31
+ url: helpers.editor_file_path(filter),
32
+ icon: "tabler/outline/code",
33
+ title: t("avo.open_in_your_editor")
34
+ ) %>
35
+ <% end %>
36
+ </div>
37
+ <%= render partial: filter.class.template, locals: {filter: filter} %>
38
+ </div>
26
39
  <% end %>
27
40
  </div>
28
41
  </div>
@@ -126,14 +126,23 @@ class Avo::ResourceComponent < Avo::BaseComponent
126
126
  def render_actions_list(actions_list)
127
127
  return unless can_see_the_actions_button?
128
128
 
129
- # `as_row_control` hydrates each action with the current record (needed for both
130
- # index rows and the single-record show/edit headers).
131
- as_row_control = @item.present?
132
-
133
129
  # Inside an index table row the hotkey must be managed by the index-row-navigator
134
130
  # (data-hotkey-original); page-level headers use a plain data-hotkey.
135
131
  as_index_row_control = row_controls_context?
136
132
 
133
+ # `as_row_control` hydrates each action with the current record (needed for both
134
+ # index rows and the single-record show/edit headers). @item is only ever set for
135
+ # the show/edit field-panel switcher, so it's blank for index rows -- fall back to
136
+ # row_controls_context? there so per-row actions_list entries get hydrated too.
137
+ as_row_control = @item.present? || as_index_row_control
138
+
139
+ # @actions is built once by the controller's set_actions, before any row exists
140
+ # (its @resource.record is nil there), so a `visible` block that inspects the
141
+ # record can't tell rows apart -- and since set_actions already dropped the
142
+ # non-visible ones, there's nothing left downstream to recover them from. Rebuild
143
+ # the list fresh from this row's own hydrated resource instead of reusing it.
144
+ actions = as_index_row_control ? row_actions : @actions
145
+
137
146
  # Actions button hotkey "a" on the main index, show and edit pages (non-nested,
138
147
  # not an index row). The index component doesn't carry a @view, so detect it by
139
148
  # class; show renders through the switcher and edit directly, both of which do
@@ -143,7 +152,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
143
152
  )
144
153
 
145
154
  render Avo::ActionsComponent.new(
146
- actions: @actions,
155
+ actions:,
147
156
  resource: @resource,
148
157
  view: @view,
149
158
  exclude: actions_list.exclude,
@@ -161,6 +170,12 @@ class Avo::ResourceComponent < Avo::BaseComponent
161
170
  )
162
171
  end
163
172
 
173
+ def row_actions
174
+ @resource.get_actions
175
+ .map { |action_bag| action_bag[:class].new(record: @resource.record, resource: @resource, view: @view, **action_bag.except(:class)) }
176
+ .select { |action| action.is_a?(Avo::Divider) || action.visible_in_view(parent_resource: @parent_resource) }
177
+ end
178
+
164
179
  def render_delete_button(control)
165
180
  # If the resource is a related resource, we use the can_delete? policy method because it uses
166
181
  # authorize_association_for(:destroy).
@@ -67,7 +67,7 @@
67
67
  <div class="sidebar-status">
68
68
  <%= link_to helpers.avo.avo_private_status_path, class: "sidebar-status__link" do %>
69
69
  <span>Avo Status</span>
70
- <span><div class="sidebar-status__indicator <%= Avo.app_status ? "sidebar-status__indicator--ok" : "sidebar-status__indicator--error" %>"></div></span>
70
+ <span><div class="sidebar-status__indicator sidebar-status__indicator--<%= Avo.app_status %>"></div></span>
71
71
  <% end %>
72
72
  </div>
73
73
  <% end %>
@@ -56,7 +56,8 @@ module Avo
56
56
  fields: @fields,
57
57
  current_user: _current_user,
58
58
  resource: @resource,
59
- query: @query
59
+ query: @query,
60
+ request: request
60
61
  )
61
62
 
62
63
  @response = performed_action.response
@@ -370,7 +370,7 @@ module Avo
370
370
  @actions = @resource
371
371
  .get_actions
372
372
  .map do |action_bag|
373
- action_bag.delete(:class).new(record: @record, resource: @resource, view: @view, **action_bag)
373
+ action_bag[:class].new(record: @record, resource: @resource, view: @view, **action_bag.except(:class))
374
374
  end
375
375
  .select do |action|
376
376
  action.is_a?(DividerComponent) || action.visible_in_view(parent_resource: @parent_resource)
@@ -28,15 +28,6 @@ module Avo
28
28
  alias_method :edit_field_wrapper, :field_wrapper
29
29
  alias_method :show_field_wrapper, :field_wrapper
30
30
 
31
- def filter_wrapper(name: nil, index: nil, **args, &block)
32
- render layout: "layouts/avo/filter_wrapper", locals: {
33
- name: name,
34
- index: index
35
- } do
36
- capture(&block)
37
- end
38
- end
39
-
40
31
  def item_selector_data_attributes(resource, controller: "")
41
32
  {
42
33
  resource_name: resource.model_key,
@@ -86,6 +86,12 @@ export default class extends Controller {
86
86
  return this.timezoneValue || this.browserZone
87
87
  }
88
88
 
89
+ // Locale from the <html lang> attribute so Luxon tokens like `cccc` translate.
90
+ // Luxon reads locale data from the browser's native Intl API, so no locale files are bundled.
91
+ get locale() {
92
+ return document.documentElement.lang || 'en'
93
+ }
94
+
89
95
  connect() {
90
96
  // Cache the initial value so we can fill it back on disconnection.
91
97
  // We do that so the JS parser will continue to work when the user hits the back button to return on this page.
@@ -125,7 +131,7 @@ export default class extends Controller {
125
131
  }
126
132
  }
127
133
 
128
- this.context.element.innerText = value.toFormat(this.formatValue)
134
+ this.context.element.innerText = value.toFormat(this.formatValue, { locale: this.locale })
129
135
  }
130
136
 
131
137
  initEdit() {
@@ -1,28 +1,25 @@
1
1
  <div
2
+ class="filters-section__body"
2
3
  data-controller="boolean-filter"
3
4
  data-filter-name="<%= filter.name %>"
4
- data-boolean-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>"
5
- >
6
- <%= filter_wrapper name: filter.name do %>
7
- <div class="flex items-center">
8
- <% if filter.options.empty? %>
9
- <div class="filters-section__empty-state"><%= filter.class.empty_message %></div>
10
- <% else %>
11
- <div class="filters-boolean-options">
12
- <% filter.options.each do |value, label| %>
13
- <label class="filters-boolean-option">
14
- <%= check_box_tag filter.id, value, filter.selected_value(value.to_s, @applied_filters),
15
- id: "avo_filters_#{filter.id.parameterize.underscore}",
16
- 'data-filter-class': filter.class,
17
- 'data-boolean-filter-target': 'option',
18
- 'data-action': 'input->boolean-filter#changeFilter'
19
- %>
20
- <span><%= label %></span>
21
- </label>
22
- <% end %>
23
- </div>
24
- <% end %>
25
- <%= link_to 'url_redirect', request.url, data: { 'boolean-filter-target': 'urlRedirect', 'turbo-frame': params[:turbo_frame] }, class: 'hidden' %>
26
- </div>
27
- <% end %>
5
+ data-boolean-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>">
6
+ <div class="flex items-center">
7
+ <% if filter.options.empty? %>
8
+ <div class="filters-section__empty-state"><%= filter.class.empty_message %></div>
9
+ <% else %>
10
+ <div class="filters-boolean-options">
11
+ <% filter.options.each do |value, label| %>
12
+ <label class="filters-boolean-option">
13
+ <%= check_box_tag filter.id, value, filter.selected_value(value.to_s, @applied_filters),
14
+ id: "avo_filters_#{filter.id.parameterize.underscore}",
15
+ "data-filter-class": filter.class,
16
+ "data-boolean-filter-target": "option",
17
+ "data-action": "input->boolean-filter#changeFilter" %>
18
+ <span><%= label %></span>
19
+ </label>
20
+ <% end %>
21
+ </div>
22
+ <% end %>
23
+ <%= link_to "url_redirect", request.url, data: {"boolean-filter-target": "urlRedirect", "turbo-frame": params[:turbo_frame]}, class: "hidden" %>
24
+ </div>
28
25
  </div>
@@ -1,39 +1,38 @@
1
- <%= content_tag :div, data: {
2
- controller: "date-time-filter",
3
- filter_name: filter.name,
4
- date_time_filter_class_value: filter.class.to_s,
5
- date_time_filter_keep_filters_panel_open_value: @resource.keep_filters_panel_open,
6
- date_time_filter_picker_options_value: filter.picker_options(@applied_filters[filter.class.to_s])
7
- } do %>
8
- <%= filter_wrapper name: filter.name do %>
9
- <div class="filters-date-input">
10
- <div class="w-full" data-slot="value">
11
- <%= text_field_tag :value, "",
12
- class: input_classes('w-full mb-0 !pe-9'),
13
- placeholder: filter.class.empty_message,
14
- data: {
15
- date_time_filter_target: "input",
16
- } %>
17
- </div>
18
-
19
- <%= content_tag :button,
20
- class: "filters-date-input__action",
21
- id: :reset,
22
- type: :button,
23
- title: t("avo.reset").capitalize,
1
+ <%= content_tag :div, class: "filters-section__body", data: {
2
+ controller: "date-time-filter",
3
+ filter_name: filter.name,
4
+ date_time_filter_class_value: filter.class.to_s,
5
+ date_time_filter_keep_filters_panel_open_value: @resource.keep_filters_panel_open,
6
+ date_time_filter_picker_options_value: filter.picker_options(@applied_filters[filter.class.to_s])
7
+ } do %>
8
+ <div class="filters-date-input">
9
+ <div class="w-full" data-slot="value">
10
+ <%= text_field_tag :value, "",
11
+ class: input_classes("w-full mb-0 !pe-9"),
12
+ placeholder: filter.class.empty_message,
13
+ autocomplete: "off",
24
14
  data: {
25
- action: "click->date-time-filter#clear",
26
- tippy: :tooltip
27
- } do %>
28
- <%= svg "tabler/outline/x", class: "filters-date-input__action-icon" %>
29
- <% end %>
15
+ date_time_filter_target: "input"
16
+ } %>
30
17
  </div>
31
18
 
32
- <div class="flex justify-end">
33
- <%= a_button style: :primary, data: { action: "date-time-filter#changeFilter" }, size: :sm do %>
34
- <%= filter.button_label || "#{t("avo.filter_by")} #{filter.name}" %>
35
- <% end %>
36
- </div>
37
- <%= link_to 'url_redirect', request.url, data: { 'date-time-filter-target': 'urlRedirect', 'turbo-frame': params[:turbo_frame] }, class: 'hidden' %>
38
- <% end %>
19
+ <%= content_tag :button,
20
+ class: "filters-date-input__action",
21
+ id: :reset,
22
+ type: :button,
23
+ title: t("avo.reset").capitalize,
24
+ data: {
25
+ action: "click->date-time-filter#clear",
26
+ tippy: :tooltip
27
+ } do %>
28
+ <%= svg "tabler/outline/x", class: "filters-date-input__action-icon" %>
29
+ <% end %>
30
+ </div>
31
+
32
+ <div class="flex justify-end">
33
+ <%= a_button style: :primary, data: {action: "date-time-filter#changeFilter"}, size: :sm do %>
34
+ <%= filter.button_label || "#{t("avo.filter_by")} #{filter.name}" %>
35
+ <% end %>
36
+ </div>
37
+ <%= link_to "url_redirect", request.url, data: {"date-time-filter-target": "urlRedirect", "turbo-frame": params[:turbo_frame]}, class: "hidden" %>
39
38
  <% end %>
@@ -1,22 +1,20 @@
1
1
  <div
2
+ class="filters-section__body"
2
3
  data-controller="multiple-select-filter"
3
4
  data-filter-name="<%= filter.name %>"
4
- data-multiple-select-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>"
5
- >
6
- <%= filter_wrapper name: filter.name do %>
7
- <%= select_tag filter.id, options_for_select(filter.options.invert, filter.selected_value(@applied_filters)),
8
- class: input_classes('filters-multi-select w-full'),
9
- multiple: true,
10
- size: filter.options.size.clamp(2, 4),
11
- id: "avo_filters_#{filter.id.parameterize.underscore}",
12
- 'data-filter-class': filter.class.to_s,
13
- 'data-multiple-select-filter-target': 'selector' %>
5
+ data-multiple-select-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>">
6
+ <%= select_tag filter.id, options_for_select(filter.options.invert, filter.selected_value(@applied_filters)),
7
+ class: input_classes("filters-multi-select w-full"),
8
+ multiple: true,
9
+ size: filter.options.size.clamp(2, 4),
10
+ id: "avo_filters_#{filter.id.parameterize.underscore}",
11
+ "data-filter-class": filter.class.to_s,
12
+ "data-multiple-select-filter-target": "selector" %>
14
13
 
15
- <div class="flex justify-end">
16
- <%= a_button style: :primary, size: :sm, data: { action: "multiple-select-filter#changeFilter" } do %>
17
- <%= filter.button_label || "#{t("avo.filter_by")} #{filter.name}" %>
18
- <% end %>
19
- </div>
20
- <%= link_to 'url_redirect', request.url, data: { 'multiple-select-filter-target': 'urlRedirect', 'turbo-frame': params[:turbo_frame] }, class: 'hidden' %>
21
- <% end %>
14
+ <div class="flex justify-end">
15
+ <%= a_button style: :primary, size: :sm, data: {action: "multiple-select-filter#changeFilter"} do %>
16
+ <%= filter.button_label || "#{t("avo.filter_by")} #{filter.name}" %>
17
+ <% end %>
18
+ </div>
19
+ <%= link_to "url_redirect", request.url, data: {"multiple-select-filter-target": "urlRedirect", "turbo-frame": params[:turbo_frame]}, class: "hidden" %>
22
20
  </div>
@@ -1,17 +1,14 @@
1
1
  <div
2
+ class="filters-section__body"
2
3
  data-controller="select-filter"
3
4
  data-filter-name="<%= filter.name %>"
4
- data-select-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>"
5
- >
6
- <%= filter_wrapper name: filter.name do %>
7
- <%= select_tag filter.id, options_for_select(filter.options.invert, filter.applied_or_default_value(@applied_filters)),
8
- class: input_classes('w-full mb-0'),
9
- include_blank: filter.class.empty_message || '—',
10
- id: "avo_filters_#{filter.id.parameterize.underscore}",
11
- 'data-filter-class': filter.class,
12
- 'data-select-filter-target': 'selector',
13
- 'data-action': "change->select-filter#changeFilter"
14
- %>
15
- <%= link_to 'url_redirect', request.url, data: { 'select-filter-target': 'urlRedirect', 'turbo-frame': params[:turbo_frame] }, class: 'hidden' %>
16
- <% end %>
5
+ data-select-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>">
6
+ <%= select_tag filter.id, options_for_select(filter.options.invert, filter.applied_or_default_value(@applied_filters)),
7
+ class: input_classes("w-full mb-0"),
8
+ include_blank: filter.class.empty_message || "—",
9
+ id: "avo_filters_#{filter.id.parameterize.underscore}",
10
+ "data-filter-class": filter.class,
11
+ "data-select-filter-target": "selector",
12
+ "data-action": "change->select-filter#changeFilter" %>
13
+ <%= link_to "url_redirect", request.url, data: {"select-filter-target": "urlRedirect", "turbo-frame": params[:turbo_frame]}, class: "hidden" %>
17
14
  </div>
@@ -1,22 +1,21 @@
1
1
  <div
2
+ class="filters-section__body"
2
3
  data-controller="text-filter"
3
4
  data-filter-name="<%= filter.name %>"
4
- data-text-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>"
5
- >
6
- <%= filter_wrapper name: filter.name do %>
7
- <%= text_field_tag filter.id, filter.applied_or_default_value(@applied_filters),
8
- class: input_classes('w-full mb-0'),
9
- id: "avo_filters_#{filter.id.parameterize.underscore}",
10
- placeholder: filter.class.empty_message,
11
- 'data-filter-class': filter.class.to_s,
12
- 'data-text-filter-target': 'text',
13
- 'data-action': 'keypress->text-filter#tryToSubmit' %>
5
+ data-text-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>">
6
+ <%= text_field_tag filter.id, filter.applied_or_default_value(@applied_filters),
7
+ class: input_classes("w-full mb-0"),
8
+ id: "avo_filters_#{filter.id.parameterize.underscore}",
9
+ placeholder: filter.class.empty_message,
10
+ autocomplete: "off",
11
+ "data-filter-class": filter.class.to_s,
12
+ "data-text-filter-target": "text",
13
+ "data-action": "keypress->text-filter#tryToSubmit" %>
14
14
 
15
- <div class="flex justify-end">
16
- <%= a_button style: :primary, data: { action: "text-filter#changeFilter" }, size: :sm do %>
17
- <%= filter.button_label || "#{t("avo.filter_by")} #{filter.name}" %>
18
- <% end %>
19
- </div>
20
- <%= link_to 'url_redirect', request.url, data: { 'text-filter-target': 'urlRedirect', 'turbo-frame': params[:turbo_frame] }, class: 'hidden' %>
21
- <% end %>
15
+ <div class="flex justify-end">
16
+ <%= a_button style: :primary, data: {action: "text-filter#changeFilter"}, size: :sm do %>
17
+ <%= filter.button_label || "#{t("avo.filter_by")} #{filter.name}" %>
18
+ <% end %>
19
+ </div>
20
+ <%= link_to "url_redirect", request.url, data: {"text-filter-target": "urlRedirect", "turbo-frame": params[:turbo_frame]}, class: "hidden" %>
22
21
  </div>
@@ -221,7 +221,8 @@ module Avo
221
221
  current_user: args[:current_user],
222
222
  resource: args[:resource],
223
223
  records: args[:query],
224
- query: args[:query]
224
+ query: args[:query],
225
+ request: args[:request]
225
226
  )
226
227
 
227
228
  self
data/lib/avo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Avo
2
- VERSION = "4.0.8" unless const_defined?(:VERSION)
2
+ VERSION = "4.0.10" unless const_defined?(:VERSION)
3
3
  end
data/lib/avo.rb CHANGED
@@ -129,7 +129,7 @@ module Avo
129
129
  end
130
130
 
131
131
  def app_status
132
- true
132
+ :ok
133
133
  end
134
134
 
135
135
  def avo_dynamic_filters_installed?
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: avo
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.8
4
+ version: 4.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Marin
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2026-07-06 00:00:00.000000000 Z
13
+ date: 2026-07-08 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -895,7 +895,6 @@ files:
895
895
  - app/views/avo/private/appearance.html.erb
896
896
  - app/views/avo/private/design.html.erb
897
897
  - app/views/avo/sidebar/_license_warning.html.erb
898
- - app/views/layouts/avo/_filter_wrapper.html.erb
899
898
  - app/views/layouts/avo/application.html.erb
900
899
  - app/views/layouts/avo/blank.html.erb
901
900
  - app/views/layouts/avo/modal.html.erb
@@ -1,8 +0,0 @@
1
- <div class="filters-section">
2
- <div class="filters-section__title">
3
- <%= name %>
4
- </div>
5
- <div class="filters-section__body">
6
- <%= yield %>
7
- </div>
8
- </div>