avo 3.0.0.pre12 → 3.0.0.pre13

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/assets/stylesheets/avo.base.css +1 -1
  4. data/app/components/avo/actions_component.html.erb +1 -1
  5. data/app/components/avo/actions_component.rb +40 -16
  6. data/app/components/avo/alert_component.html.erb +1 -1
  7. data/app/components/avo/field_wrapper_component.html.erb +2 -2
  8. data/app/components/avo/fields/common/heading_component.html.erb +1 -1
  9. data/app/components/avo/fields/markdown_field/edit_component.html.erb +3 -3
  10. data/app/components/avo/fields/markdown_field/show_component.html.erb +3 -3
  11. data/app/components/avo/fields/trix_field/edit_component.html.erb +1 -1
  12. data/app/components/avo/fields/trix_field/show_component.html.erb +1 -1
  13. data/app/components/avo/index/field_wrapper_component.html.erb +1 -1
  14. data/app/components/avo/index/grid_item_component.html.erb +9 -35
  15. data/app/components/avo/index/grid_item_component.rb +36 -10
  16. data/app/components/avo/panel_component.html.erb +1 -1
  17. data/app/components/avo/profile_item_component.html.erb +17 -2
  18. data/app/components/avo/profile_item_component.rb +13 -1
  19. data/app/components/avo/resource_component.rb +1 -0
  20. data/app/components/avo/sidebar_profile_component.html.erb +27 -27
  21. data/app/controllers/avo/actions_controller.rb +11 -1
  22. data/app/controllers/avo/associations_controller.rb +1 -1
  23. data/app/controllers/avo/base_controller.rb +14 -2
  24. data/app/controllers/avo/home_controller.rb +1 -1
  25. data/app/controllers/avo/search_controller.rb +7 -11
  26. data/app/javascript/js/controllers/fields/{simple_mde_controller.js → easy_mde_controller.js} +3 -3
  27. data/app/javascript/js/controllers/search_controller.js +3 -1
  28. data/app/javascript/js/controllers.js +2 -2
  29. data/app/views/avo/actions/show.html.erb +2 -1
  30. data/app/views/avo/partials/_profile_menu_extra.html.erb +2 -0
  31. data/lib/avo/base_action.rb +8 -1
  32. data/lib/avo/base_resource.rb +73 -102
  33. data/lib/avo/concerns/filters_session_handler.rb +0 -1
  34. data/lib/avo/concerns/has_items.rb +8 -8
  35. data/lib/avo/configuration.rb +6 -2
  36. data/lib/avo/engine.rb +5 -0
  37. data/lib/avo/fields/base_field.rb +0 -4
  38. data/lib/avo/fields/belongs_to_field.rb +14 -8
  39. data/lib/avo/fields/has_base_field.rb +1 -1
  40. data/lib/avo/resources/controls/actions_list.rb +2 -1
  41. data/lib/avo/services/debug_service.rb +1 -1
  42. data/lib/avo/version.rb +1 -1
  43. data/lib/generators/avo/eject_generator.rb +1 -0
  44. data/lib/generators/avo/install_generator.rb +0 -1
  45. data/lib/generators/avo/resource_generator.rb +4 -1
  46. data/lib/generators/avo/templates/resource/resource.tt +3 -4
  47. data/lib/tasks/avo_tasks.rake +27 -0
  48. data/public/avo-assets/avo.base.css +273 -138
  49. data/public/avo-assets/avo.base.js +245 -217
  50. data/public/avo-assets/avo.base.js.map +3 -3
  51. metadata +4 -4
  52. data/lib/avo/grid_collector.rb +0 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76b9e80370d7cd7011b456fe12affdd877ac7ea71d44ae1774ff661a2a0a5452
4
- data.tar.gz: 1ce30818dee6f87d249f1e23e5d0c7ee73c9b8e67862c77f393a7598e5d712b4
3
+ metadata.gz: cbf17f6b86663450788938a16475eba907f1a85a400f72d0efd58b59baea131c
4
+ data.tar.gz: be6d1682124dfbc7a0fdbff863b80caab538b7fdf28768d463169f2acd2fd086
5
5
  SHA512:
6
- metadata.gz: c90992abccb5192dec1ce3414f4c14bf51dc7e665a08320df9c4a154c7539b55d4bdfa4c6d23322ffc5a550b27f9eb15815298ad07b23a2e7f7fc8ed8be4f16e
7
- data.tar.gz: 390a9b9988437466ced356326a811d6cabdfa360d0982e8dfc464254cf3025bd6660d5ef1656fc44cee2d0fffcffd8f2907488379eb3d817cf6ea2493c976a72
6
+ metadata.gz: 6c47148a510c188098e524e70c1f7782fdb6fdb0ad8592f6d1e7cdf44fb175c6973db23f4672e2361be971639a5943c44309ea69409967ebe427fc558d90367c
7
+ data.tar.gz: f065601a617923ac787e0d468276d379f2cfd8798f34c9bdd604ed3a8df7c0722ded2f1013a121b0d69c9d75c85ae0b38ed7a61c745509c83264ca1502f8c9b3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (3.0.0.pre12)
4
+ avo (3.0.0.pre13)
5
5
  actionview (>= 6.1)
6
6
  active_link_to
7
7
  activerecord (>= 6.1)
@@ -1,4 +1,4 @@
1
- @import './../../../node_modules/simplemde/dist/simplemde.min.css';
1
+ @import './../../../node_modules/easymde/dist/easymde.min.css';
2
2
  @import './../../../node_modules/tippy.js/dist/tippy.css';
3
3
  @import './../../../node_modules/tippy.js/themes/light.css';
4
4
  @import './../../../node_modules/flatpickr/dist/flatpickr.css';
@@ -32,7 +32,7 @@
32
32
  >
33
33
  <div data-target="actions-list" class="w-full space divide-y">
34
34
  <% actions.each_with_index do |action, index| %>
35
- <%= link_to action_path(action.param_id),
35
+ <%= link_to action_path(action),
36
36
  data: {
37
37
  action_name: action.action_name,
38
38
  'turbo-frame': 'actions_show',
@@ -4,11 +4,12 @@ class Avo::ActionsComponent < ViewComponent::Base
4
4
  include Avo::ApplicationHelper
5
5
  attr_reader :label, :size, :as_row_control
6
6
 
7
- def initialize(actions: [], resource: nil, view: nil, exclude: [], style: :outline, color: :primary, label: nil, size: :md, as_row_control: false)
7
+ def initialize(actions: [], resource: nil, view: nil, exclude: [], include: [], style: :outline, color: :primary, label: nil, size: :md, as_row_control: false)
8
8
  @actions = actions || []
9
9
  @resource = resource
10
10
  @view = view
11
11
  @exclude = exclude
12
+ @include = include
12
13
  @color = color
13
14
  @style = style
14
15
  @label = label || I18n.t("avo.actions")
@@ -21,19 +22,25 @@ class Avo::ActionsComponent < ViewComponent::Base
21
22
  end
22
23
 
23
24
  def actions
24
- @actions.reject { |action| action.class.in?(@exclude) }
25
+ if @exclude.present?
26
+ @actions.reject { |action| action.class.in?(@exclude) }
27
+ elsif @include.present?
28
+ @actions.select { |action| action.class.in?(@include) }
29
+ else
30
+ @actions
31
+ end
25
32
  end
26
33
 
27
34
  # When running an action for one record we should do it on a special path.
28
35
  # We do that so we get the `record` param inside the action so we can prefill fields.
29
- def action_path(id)
30
- return single_record_path(id) if as_row_control
31
- return many_records_path(id) unless @resource.has_record_id?
36
+ def action_path(action)
37
+ return single_record_path(action) if as_row_control
38
+ return many_records_path(action) unless @resource.has_record_id?
32
39
 
33
40
  if on_record_page?
34
- single_record_path id
41
+ single_record_path action
35
42
  else
36
- many_records_path id
43
+ many_records_path action
37
44
  end
38
45
  end
39
46
 
@@ -54,17 +61,34 @@ class Avo::ActionsComponent < ViewComponent::Base
54
61
  !on_record_page?
55
62
  end
56
63
 
57
- def single_record_path(id)
58
- Avo::Services::URIService.parse(@resource.record_path)
59
- .append_paths("actions")
60
- .append_query(action_id: id)
61
- .to_s
64
+ def single_record_path(action)
65
+ action_url(action, @resource.record_path)
66
+ end
67
+
68
+ def many_records_path(action)
69
+ action_url(action, @resource.records_path)
62
70
  end
63
71
 
64
- def many_records_path(id)
65
- Avo::Services::URIService.parse(@resource.records_path)
72
+ def action_url(action, path)
73
+ Avo::Services::URIService.parse(path)
66
74
  .append_paths("actions")
67
- .append_query(action_id: id)
68
- .to_s
75
+ .append_query(
76
+ {
77
+ action_id: action.param_id,
78
+ arguments: encrypted_arguments(action)
79
+ }.compact
80
+ ).to_s
81
+ end
82
+
83
+ # Encrypt the arguments so we can pass them as a query param.
84
+ # EncryptionService can generate special characters that can break the URL.
85
+ # We use Base64 to encode the encrypted string so we can safely pass it as a query param and don't break the URL.
86
+ def encrypted_arguments(action)
87
+ return if action.arguments.blank?
88
+
89
+ Base64.encode64 Avo::Services::EncryptionService.encrypt(
90
+ message: action.arguments,
91
+ purpose: :action_arguments
92
+ )
69
93
  end
70
94
  end
@@ -10,7 +10,7 @@
10
10
  </div>
11
11
  <div class="ml-3 w-0 flex-1 pt-0.5">
12
12
  <p class="text-sm leading-5 font-semibold">
13
- <%== message %>
13
+ <%= sanitize message %>
14
14
  </p>
15
15
  </div>
16
16
  <div class="ml-4 flex-shrink-0 flex items-center">
@@ -24,13 +24,13 @@
24
24
  <div class="text-red-600 mt-2 text-sm"><%= record.errors.full_messages_for(field.id).to_sentence %></div>
25
25
  <% end %>
26
26
  <% if help.present? %>
27
- <div class="text-gray-600 mt-2 text-sm"><%== help %></div>
27
+ <div class="text-gray-600 mt-2 text-sm"><%= sanitize help %></div>
28
28
  <% end %>
29
29
  <% end %>
30
30
  </div>
31
31
  </div>
32
32
  <% if params[:avo_debug].present? %>
33
33
  <!-- Raw value: -->
34
- <!-- <%== field.value.inspect %> -->
34
+ <!-- <%= sanitize field.value.inspect %> -->
35
35
  <% end %>
36
36
  <% end %>
@@ -3,7 +3,7 @@
3
3
  <% if empty %>
4
4
  <% elsif value.present? %>
5
5
  <% if as_html %>
6
- <%== value %>
6
+ <%= sanitize value %>
7
7
  <% else %>
8
8
  <div class="font-semibold uppercase"><%= value %></div>
9
9
  <% end %>
@@ -1,10 +1,10 @@
1
1
  <%= field_wrapper **field_wrapper_args, full_width: true do %>
2
- <div data-controller="simple-mde">
2
+ <div data-controller="easy-mde">
3
3
  <%= @form.text_area @field.id,
4
- class: classes("w-full js-has-simple-mde-editor"),
4
+ class: classes("w-full js-has-easy-mde-editor"),
5
5
  data: {
6
6
  view: view,
7
- 'simple-mde-target': 'element',
7
+ 'easy-mde-target': 'element',
8
8
  'component-options': @field.options.to_json,
9
9
  },
10
10
  disabled: disabled?,
@@ -1,10 +1,10 @@
1
1
  <%= field_wrapper **field_wrapper_args, full_width: true do %>
2
- <div data-controller="simple-mde">
2
+ <div data-controller="easy-mde">
3
3
  <%= text_area_tag @field.id, @field.value,
4
- class: helpers.input_classes('w-full js-has-simple-mde-editor'),
4
+ class: helpers.input_classes('w-full js-has-easy-mde-editor'),
5
5
  placeholder: @field.placeholder,
6
6
  disabled: disabled?,
7
- 'data-simple-mde-target': 'element',
7
+ 'data-easy-mde-target': 'element',
8
8
  'data-component-options': @field.options.to_json,
9
9
  'data-view': :show %>
10
10
  </div>
@@ -20,7 +20,7 @@
20
20
  },
21
21
  input: trix_id,
22
22
  placeholder: @field.placeholder do %>
23
- <%== @field.value %>
23
+ <%= sanitize @field.value %>
24
24
  <% end %>
25
25
  <%= @form.text_area @field.id,
26
26
  class: classes("w-full hidden"),
@@ -8,7 +8,7 @@
8
8
  <%= link_to t('avo.show_content'), 'javascript:void(0);', class: 'font-bold inline-block', data: { action: 'click->hidden-input#showContent' } %>
9
9
  <% end %>
10
10
  <div class="<%= content_classes %> " data-hidden-input-target="content">
11
- <%== @field.value %>
11
+ <%= sanitize @field.value %>
12
12
  </div>
13
13
  </div>
14
14
  <% end %>
@@ -19,6 +19,6 @@
19
19
  <% end %>
20
20
  <% if params[:avo_debug].present? %>
21
21
  <!-- Raw value: -->
22
- <!-- <%== @field.value.inspect %> -->
22
+ <!-- <%= sanitize @field.value.inspect %> -->
23
23
  <% end %>
24
24
  <% end %>
@@ -5,45 +5,19 @@
5
5
  data-resource-name="<%= @resource.class.to_s %>"
6
6
  data-record-id="<%= @resource.record.id %>"
7
7
  >
8
- <%= content_tag :div, class: "relative w-full pb-3/4 rounded-t overflow-hidden #{cover.present? ? cover.get_html(:classes, view: :index, element: :wrapper) : ""}", style: cover.present? ? cover.get_html(:style, view: :index, element: :wrapper) : "" do %>
9
- <% if @resource.record_selector %>
10
- <%== item_selector_input floating: true, size: :lg %>
11
- <% end %>
12
- <% if cover.blank? %>
13
- <%= link_to resource_view_path do %>
14
- <%= render Avo::Index::GridCoverEmptyStateComponent.new %>
15
- <% end %>
16
- <% elsif cover.respond_to?(:to_image) && cover.to_image.present? %>
17
- <%= link_to_if cover.link_to_resource, image_tag(cover.to_image, class: 'absolute h-full w-full object-cover'), resource_view_path, class: 'absolute h-full w-full object-cover', title: title.value %>
18
- <% elsif cover.type == 'file' %>
19
- <% if cover.value.attached? && cover.value.representable? %>
20
- <%= link_to_if cover.link_to_resource, image_tag(helpers.main_app.url_for(cover.value.variant(resize_to_limit: [480, 480])), class: 'absolute h-full w-full object-cover'), resource_view_path, class: 'absolute h-full w-full object-cover', title: title.value %>
21
- <% else %>
22
- <%= link_to resource_view_path do %>
23
- <%= render Avo::Index::GridCoverEmptyStateComponent.new %>
24
- <% end %>
25
- <% end %>
26
- <% elsif cover.value.present? %>
27
- <%= link_to_if cover.link_to_resource, image_tag(cover.value, class: 'absolute h-full w-full object-cover'), resource_view_path, class: 'absolute h-full w-full object-cover', title: title.value %>
28
- <% else %>
29
- <%= render Avo::Index::GridCoverEmptyStateComponent.new %>
30
- <% end %>
8
+ <%= content_tag :div,
9
+ class: "relative w-full pb-3/4 rounded-t overflow-hidden #{html(:cover, :classes)}",
10
+ style: html(:cover, :style) do %>
11
+ <%== item_selector_input(floating: true, size: :lg) if @resource.record_selector%>
12
+ <%= render_cover %>
31
13
  <% end %>
32
- <%= content_tag :div, class: "grid grid-cols-1 place-content-between p-4 h-full" do %>
14
+ <div class="grid grid-cols-1 place-content-between p-4 h-full">
33
15
  <div class="mb-4 h-full flex flex-col space-between">
34
- <% if title.present? %>
35
- <%= content_tag :div, class: "grid font-semibold leading-tight text-lg mb-2 #{title.get_html(:classes, view: :index, element: :wrapper)}", style: title.get_html(:style, view: :index, element: :wrapper) do %>
36
- <%= link_to_if title.link_to_resource, title.value, resource_view_path %>
37
- <% end %>
38
- <% end %>
39
- <% if body.present? %>
40
- <%= content_tag :div, class: "text-sm break-words text-gray-500 #{body.get_html(:classes, view: :index, element: :wrapper)}", style: body.get_html(:style, view: :index, element: :wrapper) do %>
41
- <%= body.value %>
42
- <% end %>
43
- <% end %>
16
+ <%= render_title %>
17
+ <%= render_body %>
44
18
  </div>
45
19
  <div class="w-full place-self-end">
46
20
  <%= render(Avo::Index::ResourceControlsComponent.new(resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, view_type: :grid, actions: actions)) %>
47
21
  </div>
48
- <% end %>
22
+ </div>
49
23
  </div>
@@ -2,30 +2,26 @@
2
2
 
3
3
  class Avo::Index::GridItemComponent < Avo::BaseComponent
4
4
  include Avo::ResourcesHelper
5
+ include Avo::Fields::Concerns::HasHTMLAttributes
5
6
 
6
7
  attr_reader :parent_resource, :actions
7
8
 
8
9
  def initialize(resource: nil, reflection: nil, parent_record: nil, parent_resource: nil, actions: nil)
9
10
  @resource = resource
10
11
  @reflection = reflection
11
- @grid_fields = resource.get_grid_fields
12
12
  @parent_record = parent_record
13
13
  @parent_resource = parent_resource
14
14
  @actions = actions
15
+ @card = Avo::ExecutionContext.new(target: resource.grid_view[:card], resource: resource, record: resource.record).handle
16
+ @whole_html = Avo::ExecutionContext.new(target: resource.grid_view[:html], resource: resource, record: resource.record).handle
15
17
  end
16
18
 
17
19
  private
18
20
 
19
- def cover
20
- @grid_fields.cover_field
21
- end
22
-
23
- def title
24
- @grid_fields.title_field
25
- end
21
+ def html(element, type)
22
+ return "" if @whole_html.nil? || (@html = @whole_html[element]).nil?
26
23
 
27
- def body
28
- @grid_fields.body_field
24
+ get_html(type, view: :index, element: :wrapper)
29
25
  end
30
26
 
31
27
  def resource_view_path
@@ -40,4 +36,34 @@ class Avo::Index::GridItemComponent < Avo::BaseComponent
40
36
 
41
37
  helpers.resource_view_path(record: @resource.record, resource: parent_or_child_resource, **args)
42
38
  end
39
+
40
+ def render_cover
41
+ return link_to_cover if @card[:cover_url].present?
42
+
43
+ link_to resource_view_path do
44
+ render Avo::Index::GridCoverEmptyStateComponent.new
45
+ end
46
+ end
47
+
48
+ def link_to_cover
49
+ classes = "absolute h-full w-full object-cover"
50
+
51
+ link_to image_tag(@card[:cover_url], class: classes), resource_view_path, class: classes, title: @card[:title]
52
+ end
53
+
54
+ def render_title
55
+ return if @card[:title].blank?
56
+
57
+ content_tag :div, class: "grid font-semibold leading-tight text-lg mb-2 #{html(:title, :classes)}", style: html(:title, :style) do
58
+ link_to @card[:title], resource_view_path
59
+ end
60
+ end
61
+
62
+ def render_body
63
+ return if @card[:body].blank?
64
+
65
+ content_tag :div, class: "text-sm break-words text-gray-500 #{html(:body, :classes)}", style: html(:body, :style) do
66
+ @card[:body]
67
+ end
68
+ end
43
69
  end
@@ -25,7 +25,7 @@
25
25
  <% end %>
26
26
  <% if body? %>
27
27
  <div data-target="panel-body" class="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:gap-4 w-full">
28
- <div class="relative flex-1 <% if sidebar? %> w-2/3 <% else %> w-full <% end %>">
28
+ <div class="relative flex-1 w-full <% if sidebar? %> sm:w-2/3 <% end %>">
29
29
  <% # The body is wrapped inside another div in order to avoid long & tall panels next to sidebars when the sidebar taller. %>
30
30
  <div class="relative <%= white_panel_classes %> <%= @body_classes %>">
31
31
  <%= body %>
@@ -1,3 +1,18 @@
1
- <%= link_to path, class: "flex-1 flex items-center justify-center bg-white text-left cursor-pointer text-gray-800 font-semibold hover:bg-primary-100 block px-4 py-1 w-full py-3 text-center rounded w-full", target: target, title: title do %>
2
- <%= helpers.svg(icon, class: 'h-4 mr-1') if icon.present? %> <%= label %>
1
+ <% if method.present? %>
2
+ <%= button_to path,
3
+ form_class: "flex-1 flex",
4
+ class: button_classes,
5
+ target: target,
6
+ title: title,
7
+ method: method,
8
+ params: params do %>
9
+ <%= helpers.svg(icon, class: 'h-4 mr-1') if icon.present? %> <%= label %>
10
+ <% end %>
11
+ <% else %>
12
+ <%= link_to path,
13
+ class: button_classes,
14
+ target: target,
15
+ title: title do %>
16
+ <%= helpers.svg(icon, class: 'h-4 mr-1') if icon.present? %> <%= label %>
17
+ <% end %>
3
18
  <% end %>
@@ -6,17 +6,29 @@ class Avo::ProfileItemComponent < ViewComponent::Base
6
6
  attr_reader :path
7
7
  attr_reader :active
8
8
  attr_reader :target
9
+ attr_reader :method
10
+ attr_reader :params
11
+ attr_reader :classes
9
12
 
10
- def initialize(label: nil, icon: nil, path: nil, active: :inclusive, target: nil, title: nil)
13
+ def initialize(label: nil, icon: nil, path: nil, active: :inclusive, target: nil, title: nil, method: nil, params: {}, classes: "")
11
14
  @label = label
12
15
  @icon = icon
13
16
  @path = path
14
17
  @active = active
15
18
  @target = target
16
19
  @title = title
20
+ @method = method
21
+ @params = params
22
+ @classes = classes
17
23
  end
18
24
 
19
25
  def title
20
26
  @title || @label
21
27
  end
28
+
29
+ private
30
+
31
+ def button_classes
32
+ "flex-1 flex items-center justify-center bg-white text-left cursor-pointer text-gray-800 font-semibold hover:bg-primary-100 block px-4 py-1 w-full py-3 text-center rounded w-full"
33
+ end
22
34
  end
@@ -165,6 +165,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
165
165
  resource: @resource,
166
166
  view: @view,
167
167
  exclude: actions_list.exclude,
168
+ include: actions_list.include,
168
169
  style: actions_list.style,
169
170
  color: actions_list.color,
170
171
  label: actions_list.label,
@@ -16,32 +16,32 @@
16
16
  <% end %>
17
17
  </div>
18
18
  </div>
19
- <% if can_destroy_user? %>
20
- <div class="relative" data-controller="toggle">
21
- <a class="flex items-center h-full cursor-pointer" data-control="profile-dots" data-action="click->toggle#togglePanel">
22
- <%= helpers.svg 'three-dots', class: 'h-4' %>
23
- </a>
24
- <div
25
- class="hidden absolute flex flex-col inset-auto right-0 -mt-12 bg-white rounded min-w-[200px] shadow-context -translate-y-full z-40"
26
- data-toggle-target="panel"
27
- data-transition-enter="transition ease-out duration-100"
28
- data-transition-enter-start="transform opacity-0 translate-y-1"
29
- data-transition-enter-end="transform opacity-100 translate-y-0"
30
- data-transition-leave="transition ease-in duration-75"
31
- data-transition-leave-start="transform opacity-100 translate-y-0"
32
- data-transition-leave-end="transform opacity-0 translate-y-1"
33
- >
34
- <% if Avo.plugin_manager.installed?(:avo_menu) && Avo::App.has_profile_menu? %>
35
- <div class="text-black space-y-4">
36
- <% Avo::App.profile_menu.items.each do |item| %>
37
- <% if item.is_a? AvoMenu::Link %>
38
- <%= render Avo::ProfileItemComponent.new label: item.name, path: item.path, icon: item.icon %>
39
- <% end %>
19
+ <div class="relative" data-controller="toggle">
20
+ <a class="flex items-center h-full cursor-pointer" data-control="profile-dots" data-action="click->toggle#togglePanel">
21
+ <%= helpers.svg 'three-dots', class: 'h-4' %>
22
+ </a>
23
+ <div
24
+ class="hidden absolute flex flex-col inset-auto right-0 -mt-12 bg-white rounded min-w-[200px] shadow-context -translate-y-full z-40"
25
+ data-toggle-target="panel"
26
+ data-transition-enter="transition ease-out duration-100"
27
+ data-transition-enter-start="transform opacity-0 translate-y-1"
28
+ data-transition-enter-end="transform opacity-100 translate-y-0"
29
+ data-transition-leave="transition ease-in duration-75"
30
+ data-transition-leave-start="transform opacity-100 translate-y-0"
31
+ data-transition-leave-end="transform opacity-0 translate-y-1"
32
+ >
33
+ <% if Avo.plugin_manager.installed?(:avo_menu) && Avo::App.has_profile_menu? %>
34
+ <div class="text-black space-y-4">
35
+ <% Avo::App.profile_menu.items.each do |item| %>
36
+ <% if item.is_a? AvoMenu::Link %>
37
+ <%= render Avo::ProfileItemComponent.new label: item.name, path: item.path, icon: item.icon %>
40
38
  <% end %>
41
- </div>
42
- <% end %>
43
- <%# Example link below %>
44
- <%#= render Avo::ProfileItemComponent.new label: 'Profile', path: '/profile', icon: 'user-circle' %>
39
+ <% end %>
40
+ </div>
41
+ <% end %>
42
+ <%# Example link below %>
43
+ <%#= render Avo::ProfileItemComponent.new label: 'Profile', path: '/profile', icon: 'user-circle' %>
44
+ <% if can_destroy_user? %>
45
45
  <%= button_to helpers.main_app.send(destroy_user_session_path),
46
46
  form: { "data-turbo" => "false" },
47
47
  method: :delete,
@@ -52,7 +52,7 @@
52
52
  form_class: 'flex-1' do %>
53
53
  <%= helpers.svg 'logout', class: 'h-4 mr-1' %> <%= t('avo.sign_out') %>
54
54
  <% end %>
55
- </div>
55
+ <% end %>
56
56
  </div>
57
- <% end %>
57
+ </div>
58
58
  </div>
@@ -56,7 +56,7 @@ module Avo
56
56
  resource: @resource,
57
57
  user: _current_user,
58
58
  view: @view,
59
- arguments: @resource.get_action_arguments(action_class)
59
+ arguments: decrypted_arguments || {}
60
60
  )
61
61
  end
62
62
 
@@ -113,6 +113,16 @@ module Avo
113
113
  )
114
114
  end
115
115
 
116
+ def decrypted_arguments
117
+ arguments = params[:arguments] || params.dig(:fields, :arguments)
118
+ return if arguments.blank?
119
+
120
+ Avo::Services::EncryptionService.decrypt(
121
+ message: Base64.decode64(arguments),
122
+ purpose: :action_arguments
123
+ )
124
+ end
125
+
116
126
  def flash_messages(messages)
117
127
  messages.each do |message|
118
128
  flash[message[:type]] = message[:body]
@@ -54,7 +54,7 @@ module Avo
54
54
  end
55
55
 
56
56
  @options = query.all.map do |record|
57
- [record.send(@attachment_resource.class.title), record.id]
57
+ [@attachment_resource.hydrate(record: record).record_title, record.id]
58
58
  end
59
59
  end
60
60
  end
@@ -313,8 +313,20 @@ module Avo
313
313
  @index_params[:sort_direction] = params[:sort_direction] || :desc
314
314
 
315
315
  # View types
316
- @index_params[:view_type] = params[:view_type] || @resource.default_view_type || Avo.configuration.default_view_type
317
- @index_params[:available_view_types] = @resource.available_view_types
316
+ available_view_types = @resource.available_view_types
317
+ @index_params[:available_view_types] = available_view_types
318
+
319
+ @index_params[:view_type] = if params[:view_type].present?
320
+ params[:view_type]
321
+ elsif available_view_types.size == 1
322
+ available_view_types.first
323
+ else
324
+ @resource.default_view_type || Avo.configuration.default_view_type
325
+ end
326
+
327
+ if available_view_types.exclude? @index_params[:view_type].to_sym
328
+ raise "View type '#{@index_params[:view_type]}' is unavailable for #{@resource.class}."
329
+ end
318
330
  end
319
331
 
320
332
  def set_filters
@@ -14,7 +14,7 @@ module Avo
14
14
  redirect_to computed_path
15
15
  elsif !Rails.env.development?
16
16
  @page_title = "Get started"
17
- resource = Avo::App.resources.min_by { |resource| resource.model_key }
17
+ resource = Avo::Current.app.resource_manager.all.min_by { |resource| resource.model_key }
18
18
  redirect_to resources_path(resource: resource)
19
19
  end
20
20
  end
@@ -37,11 +37,11 @@ module Avo
37
37
  query = Avo::ExecutionContext.new(
38
38
  target: resource.search_query,
39
39
  params: params,
40
- query: resource.class.scope
40
+ query: resource.class.query_scope
41
41
  ).handle
42
42
 
43
43
  # Get the count
44
- results_count = query.reselect(:id).count
44
+ results_count = query.reselect(resource.model_class.primary_key).count
45
45
 
46
46
  # Get the results
47
47
  query = query.limit(8)
@@ -58,7 +58,7 @@ module Avo
58
58
 
59
59
  result_object = {
60
60
  header: header,
61
- help: resource.class.search_query_help,
61
+ help: resource.fetch_search(:help) || "",
62
62
  results: results,
63
63
  count: results.count
64
64
  }
@@ -112,21 +112,17 @@ module Avo
112
112
  # resource = avo_resource.dup.hydrate(record: record).hydrate_fields
113
113
  resource = avo_resource.dup.hydrate(record: record)
114
114
 
115
- fetch_result_information record, resource
115
+ fetch_result_information record, resource, resource.fetch_search(:item)
116
116
  end
117
117
  end
118
118
 
119
119
  private
120
120
 
121
- def fetch_result_information(record, resource)
121
+ def fetch_result_information(record, resource, item)
122
122
  {
123
123
  _id: record.id,
124
- _label: resource.label,
125
- _url: Avo::ExecutionContext.new(
126
- target: resource.search_result_path || resource.record_path,
127
- resource: resource,
128
- record: record
129
- ).handle,
124
+ _label: item&.dig(:title) || resource.record_title,
125
+ _url: resource.fetch_search(:result_path) || resource.record_path
130
126
  }
131
127
  end
132
128
 
@@ -1,5 +1,5 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
- import SimpleMDE from 'simplemde'
2
+ import EasyMDE from 'easymde'
3
3
 
4
4
  export default class extends Controller {
5
5
  static targets = ['element']
@@ -27,9 +27,9 @@ export default class extends Controller {
27
27
  options.status = false
28
28
  }
29
29
 
30
- const simpleMde = new SimpleMDE(options)
30
+ const easyMde = new EasyMDE(options)
31
31
  if (this.view === 'show') {
32
- simpleMde.codemirror.options.readOnly = true
32
+ easyMde.codemirror.options.readOnly = true
33
33
  }
34
34
  }
35
35
  }
@@ -211,7 +211,9 @@ export default class extends Controller {
211
211
  searchUrl(query) {
212
212
  const url = URI()
213
213
 
214
- return url.segment([window.Avo.configuration.root_path, ...this.searchSegments()]).search(this.searchParams(query)).readable().toString()
214
+ return url.segment([window.Avo.configuration.root_path, ...this.searchSegments()])
215
+ .search(this.searchParams(encodeURIComponent(query)))
216
+ .readable().toString()
215
217
  }
216
218
 
217
219
  searchSegments() {