avo 1.1.0 → 1.2.6.pre.1

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -1
  3. data/Gemfile.lock +10 -2
  4. data/app/components/avo/index/grid_cover_empty_state_component.html.erb +3 -0
  5. data/app/components/avo/index/grid_cover_empty_state_component.rb +4 -0
  6. data/app/components/avo/index/grid_item_component.html.erb +4 -4
  7. data/app/components/avo/modal_component.rb +2 -1
  8. data/app/components/avo/panel_component.html.erb +7 -2
  9. data/app/components/avo/panel_component.rb +4 -1
  10. data/app/components/avo/show/fields/has_one_field_component.html.erb +3 -3
  11. data/app/components/avo/views/resource_edit_component.html.erb +3 -3
  12. data/app/components/avo/views/resource_index_component.html.erb +4 -4
  13. data/app/components/avo/views/resource_new_component.html.erb +3 -3
  14. data/app/components/avo/views/resource_show_component.html.erb +2 -2
  15. data/app/controllers/avo/application_controller.rb +20 -0
  16. data/app/controllers/avo/base_controller.rb +16 -0
  17. data/app/controllers/avo/home_controller.rb +5 -1
  18. data/app/helpers/avo/application_helper.rb +0 -6
  19. data/app/packs/entrypoints/application.css +1 -0
  20. data/app/packs/stylesheets/breadcrumbs.css +12 -0
  21. data/app/packs/stylesheets/components/code.css +5 -0
  22. data/app/packs/svgs/chevron-right.svg +3 -0
  23. data/app/views/avo/actions/show.html.erb +2 -2
  24. data/app/views/avo/home/_actions.html.erb +5 -5
  25. data/app/views/avo/home/_docs.html.erb +1 -1
  26. data/app/views/avo/home/_filters.html.erb +4 -4
  27. data/app/views/avo/home/_resources.html.erb +15 -2
  28. data/app/views/avo/home/index.html.erb +4 -4
  29. data/app/views/avo/relations/new.html.erb +2 -2
  30. data/app/views/avo/sidebar/_sidebar.html.erb +2 -1
  31. data/app/views/layouts/avo/application.html.erb +3 -2
  32. data/avo.gemspec +2 -0
  33. data/lib/avo.rb +4 -0
  34. data/lib/avo/app.rb +21 -11
  35. data/lib/avo/base_resource.rb +1 -0
  36. data/lib/avo/configuration.rb +17 -1
  37. data/lib/avo/grid_collector.rb +1 -1
  38. data/lib/avo/licensing/h_q.rb +3 -3
  39. data/lib/avo/licensing/license.rb +2 -0
  40. data/lib/avo/licensing/pro_license.rb +2 -1
  41. data/lib/avo/version.rb +1 -1
  42. data/lib/generators/avo/templates/initializer/avo.tt +10 -1
  43. data/lib/generators/avo/templates/tool/view.tt +3 -3
  44. data/lib/generators/avo/tool_generator.rb +5 -3
  45. data/public/avo-packs/css/{application-9d115b7e.css → application-b5f285ce.css} +49 -7
  46. data/public/avo-packs/css/application-b5f285ce.css.br +0 -0
  47. data/public/avo-packs/css/application-b5f285ce.css.gz +0 -0
  48. data/public/avo-packs/css/application-b5f285ce.css.map +1 -0
  49. data/public/avo-packs/css/application-b5f285ce.css.map.br +0 -0
  50. data/public/avo-packs/css/application-b5f285ce.css.map.gz +0 -0
  51. data/public/avo-packs/js/application-e351ef988a3cb9687542.js.map +1 -1
  52. data/public/avo-packs/js/application-e351ef988a3cb9687542.js.map.br +0 -0
  53. data/public/avo-packs/js/application-e351ef988a3cb9687542.js.map.gz +0 -0
  54. data/public/avo-packs/manifest.json +7 -7
  55. metadata +42 -10
  56. data/public/avo-packs/css/application-9d115b7e.css.br +0 -0
  57. data/public/avo-packs/css/application-9d115b7e.css.gz +0 -0
  58. data/public/avo-packs/css/application-9d115b7e.css.map +0 -1
  59. data/public/avo-packs/css/application-9d115b7e.css.map.br +0 -0
  60. data/public/avo-packs/css/application-9d115b7e.css.map.gz +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1535b91dc05b50bd35b9842c10c5ed266edaec91eced513b8102776637f21fd2
4
- data.tar.gz: b1ed85745075ff3aac0f32170c39323a6b070b38649d43470222cc56d554cff9
3
+ metadata.gz: 14e502a4a38c39f136298b688cb7df3be49612444de569c358aff07828c5ab7d
4
+ data.tar.gz: cf7bdebe1e32e73021c1b78a7200887b26e766471300ff6645c62353c9b9f1fd
5
5
  SHA512:
6
- metadata.gz: 14461575f33ab286f132e5b1965c2b504f30edec5ec97cb8695333f63ba93ee08ac9dcb91b1a54fc5cf4421e850018b5298cc76d79586674b5eca722fd1fa30b
7
- data.tar.gz: c845a9413d258f181a40ea7b0ec816ce2c267c1e0c2d369d131c610e4d65085169945482cd3746de0cc5bb85d7eade7f9ef05cd28a75f2e183a9b938e09c7c36
6
+ metadata.gz: 412610059431101fdebbcadeee2618941aa072ace0305937918ff78127409288e23109f4a3d4653d4a7a6495aa25a78066dc5f8e37ca1654bfe53aa5abed086a
7
+ data.tar.gz: 943c91cf7c347952a8b7c9da989b23d8ed45d6362547e2bc23524a0725008f87fc8f23ab9ad31f380564efd61fd5aee30b8c864def7eb9998a217e4f1bb81c01
data/Gemfile CHANGED
@@ -52,7 +52,7 @@ gem "byebug", platforms: [:mri, :mingw, :x64_mingw]
52
52
  gem "dotenv-rails"
53
53
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
54
54
  gem "web-console", ">= 3.3.0"
55
- gem "listen", ">= 3.3.0"
55
+ gem "listen", ">= 3.5.1"
56
56
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
57
57
  gem "spring"
58
58
 
@@ -119,3 +119,7 @@ gem "view_component", require: "view_component/engine"
119
119
  gem "addressable"
120
120
 
121
121
  gem "appraisal"
122
+
123
+ gem 'meta-tags'
124
+
125
+ gem 'breadcrumbs_on_rails'
data/Gemfile.lock CHANGED
@@ -1,13 +1,15 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (1.1.0)
4
+ avo (1.2.6.pre.1)
5
5
  active_link_to
6
6
  addressable
7
+ breadcrumbs_on_rails
7
8
  countries
8
9
  hotwire-rails
9
10
  httparty
10
11
  image_processing
12
+ meta-tags
11
13
  pagy
12
14
  pundit
13
15
  rails (>= 6.0)
@@ -87,6 +89,8 @@ GEM
87
89
  bindex (0.8.1)
88
90
  bootsnap (1.4.8)
89
91
  msgpack (~> 1.0)
92
+ breadcrumbs_on_rails (4.0.0)
93
+ rails (>= 5.0)
90
94
  builder (3.2.4)
91
95
  bump (0.9.0)
92
96
  byebug (11.1.3)
@@ -161,6 +165,8 @@ GEM
161
165
  mail (2.7.1)
162
166
  mini_mime (>= 0.1.1)
163
167
  marcel (1.0.0)
168
+ meta-tags (2.14.0)
169
+ actionpack (>= 3.2.0, < 6.2)
164
170
  method_source (1.0.0)
165
171
  mime-types (3.3.1)
166
172
  mime-types-data (~> 3.2015)
@@ -356,6 +362,7 @@ DEPENDENCIES
356
362
  appraisal
357
363
  avo!
358
364
  bootsnap (>= 1.4.2)
365
+ breadcrumbs_on_rails
359
366
  bump
360
367
  byebug
361
368
  capybara (>= 2.15)
@@ -372,7 +379,8 @@ DEPENDENCIES
372
379
  image_processing (~> 1.2)
373
380
  iso
374
381
  jbuilder (~> 2.7)
375
- listen (>= 3.3.0)
382
+ listen (>= 3.5.1)
383
+ meta-tags
376
384
  pg (>= 0.18, < 2.0)
377
385
  puma (~> 4.3.5)
378
386
  pundit
@@ -0,0 +1,3 @@
1
+ <div class="absolute bg-gray-100 w-full h-full">
2
+ <%= helpers.svg 'avocado', class: 'relative transform -translate-x-1/2 -translate-y-1/2 h-20 text-gray-400 inset-auto top-1/2 left-1/2' %>
3
+ </div>
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Index::GridCoverEmptyStateComponent < ViewComponent::Base
4
+ end
@@ -4,7 +4,9 @@
4
4
  <div class="relative w-full pb-3/4 rounded-t-xl overflow-hidden">
5
5
  <%== item_selector_input floating: true, size: :lg %>
6
6
 
7
- <% if cover.respond_to? :to_image %>
7
+ <% if cover.blank? %>
8
+ <%= render Avo::Index::GridCoverEmptyStateComponent.new %>
9
+ <% elsif cover.respond_to? :to_image %>
8
10
  <%= link_to_if cover.link_to_resource, image_tag(cover.to_image, class: 'absolute h-full w-full object-cover'), helpers.resource_path(@resource.model), class: 'absolute h-full w-full object-cover', title: title.value %>
9
11
  <% elsif cover.type == 'file' %>
10
12
  <% if cover.value.attached? && cover.value.representable? %>
@@ -18,9 +20,7 @@
18
20
  <% if cover.value.present? %>
19
21
  <%= link_to_if cover.link_to_resource, image_tag(cover.value, class: 'absolute h-full w-full object-cover'), helpers.resource_path(@resource.model), class: 'absolute h-full w-full object-cover', title: title.value %>
20
22
  <% else %>
21
- <div class="absolute bg-gray-100 w-full h-full">
22
- <%= helpers.svg 'avocado', class: 'relative transform -translate-x-1/2 -translate-y-1/2 h-20 text-gray-400 inset-auto top-1/2 left-1/2' %>
23
- </div>
23
+ <%= render Avo::Index::GridCoverEmptyStateComponent.new %>
24
24
  <% end %>
25
25
  <% end %>
26
26
  </div>
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::ModalComponent < ViewComponent::Base
4
- with_content_areas :heading, :controls
4
+ renders_one :heading
5
+ renders_one :controls
5
6
  end
@@ -1,7 +1,12 @@
1
1
  <div <%== data_attributes %>>
2
2
  <div class="mb-6">
3
- <div class="text-2xl tracking-normal font-bold text-gray-800 truncate mb-3">
4
- <%= @title %>
3
+ <div>
4
+ <div class="breadcrumbs">
5
+ <%= helpers.render_breadcrumbs(separator: helpers.svg('chevron-right', class: 'inline-block h-3 stroke-current relative top-[-1px] ml-1' )) if Avo.configuration.display_breadcrumbs %>
6
+ </div>
7
+ <div class="text-2xl tracking-normal font-bold text-gray-800 truncate">
8
+ <%= @title %>
9
+ </div>
5
10
  </div>
6
11
 
7
12
  <div>
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::PanelComponent < ViewComponent::Base
4
- with_content_areas :tools, :body, :bare_content, :footer
4
+ renders_one :tools
5
+ renders_one :body
6
+ renders_one :bare_content
7
+ renders_one :footer
5
8
 
6
9
  def initialize(title: nil, body_classes: nil, data: {})
7
10
  @title = title
@@ -3,14 +3,14 @@
3
3
  <%= render(Avo::LoadingComponent.new(title: @field.name)) %>
4
4
  </turbo-frame>
5
5
  <% else %>
6
- <%= panel(title: @field.id.capitalize) do |c| %>
7
- <% c.with :tools do %>
6
+ <%= render Avo::PanelComponent.new(title: @field.id.capitalize) do |c| %>
7
+ <% c.tools do %>
8
8
  <%= a_link helpers.resource_attach_path(@resource.model.model_name.route_key, @resource.model.id, @field.id), color: 'indigo', 'data-turbo-frame': 'attach_modal' do %>
9
9
  <%= svg 'view-grid-add' %> <%= t('avo.attach_item', item: @field.id).capitalize %>
10
10
  <% end %>
11
11
  <% end %>
12
12
 
13
- <% c.with :body do %>
13
+ <% c.body do %>
14
14
  <div class="py-8 flex justify-center items-center">
15
15
  <%= empty_state @field.id %>
16
16
  </div>
@@ -3,8 +3,8 @@
3
3
  <%= form_with model: @resource.model, url: helpers.resource_path(@resource.model), method: :put, multipart: true do |form| %>
4
4
  <%= hidden_field_tag :referrer, back_path if params[:via_resource_class] %>
5
5
 
6
- <%= panel(title: resource_panel[:name]) do |component| %>
7
- <% component.with :tools do %>
6
+ <%= render Avo::PanelComponent.new(title: resource_panel[:name]) do |c| %>
7
+ <% c.tools do %>
8
8
  <div class="flex justify-end space-x-2">
9
9
  <%= a_link back_path do %>
10
10
  <%= helpers.svg 'arrow-left' %> <%= t('avo.cancel').capitalize %>
@@ -18,7 +18,7 @@
18
18
  </div>
19
19
  <% end %>
20
20
 
21
- <% component.with :body do %>
21
+ <% c.body do %>
22
22
  <% @resource.get_fields.each_with_index do |field, index| %>
23
23
  <%= edit_field field, index, @resource, form unless field.computed %>
24
24
  <% end %>
@@ -1,5 +1,5 @@
1
- <%= panel title: title, body_classes: 'py-4', data: { component: 'resources-index' } do |c| %>
2
- <% c.with :tools do %>
1
+ <%= render Avo::PanelComponent.new title: title, body_classes: 'py-4', data: { component: 'resources-index' } do |c| %>
2
+ <% c.tools do %>
3
3
  <%= render 'actions' if @actions.present? %>
4
4
 
5
5
  <% if can_create? %>
@@ -21,7 +21,7 @@
21
21
  <% end %>
22
22
  <% end %>
23
23
 
24
- <% c.with :body do %>
24
+ <% c.body do %>
25
25
  <div class="flex justify-between py-6 min-h-24"
26
26
  data-selected-resources-name="<%= @resource.plural_name.downcase %>"
27
27
  data-selected-resources="[]"
@@ -51,7 +51,7 @@
51
51
  <% end %>
52
52
 
53
53
  <% if view_type.to_sym == :grid %>
54
- <% c.with :bare_content do %>
54
+ <% c.bare_content do %>
55
55
  <%= render Avo::Index::ResourceGridComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_model: @parent_model) %>
56
56
 
57
57
  <div class="bg-white rounded-xl shadow-xl mt-8 py-6">
@@ -1,8 +1,8 @@
1
1
  <div>
2
2
  <% @resource.panels.each do |resource_panel| %>
3
3
  <%= form_with model: @resource.model, url: helpers.resources_path(@resource.model, via_relation_class: params[:via_relation_class], via_resource_id: params[:via_resource_id]), local: true, multipart: true do |form| %>
4
- <%= panel(title: resource_panel[:name]) do |component| %>
5
- <% component.with :tools do %>
4
+ <%= render Avo::PanelComponent.new(title: resource_panel[:name]) do |c| %>
5
+ <% c.tools do %>
6
6
  <div class="flex justify-end space-x-2">
7
7
  <%= a_link back_path do %>
8
8
  <%= svg 'arrow-left' %> <%= t('avo.cancel').capitalize %>
@@ -14,7 +14,7 @@
14
14
  </div>
15
15
  <% end %>
16
16
 
17
- <% component.with :body do %>
17
+ <% c.body do %>
18
18
  <% @resource.get_fields.each_with_index do |field, index| %>
19
19
  <%= edit_field field, index, @resource, form unless field.computed %>
20
20
  <% end %>
@@ -4,7 +4,7 @@
4
4
  >
5
5
  <% @resource.panels.each do |resource_panel| %>
6
6
  <%= render(Avo::PanelComponent.new(title: resource_panel[:name])) do |c| %>
7
- <% c.with :tools do %>
7
+ <% c.tools do %>
8
8
  <% if resource_panel[:name] == @resource.default_panel_name %>
9
9
  <%= render 'actions' %>
10
10
 
@@ -44,7 +44,7 @@
44
44
  <% end %>
45
45
  <% end %>
46
46
 
47
- <% c.with :body do %>
47
+ <% c.body do %>
48
48
  <% if fields_by_panel[resource_panel[:name]].present? %>
49
49
  <% fields_by_panel[resource_panel[:name]].each_with_index do |field, index| %>
50
50
  <%= show_field field, index, @resource %>
@@ -4,9 +4,11 @@ module Avo
4
4
  include Pagy::Backend
5
5
  protect_from_forgery with: :exception
6
6
  before_action :init_app
7
+ before_action :check_avo_license
7
8
  before_action :set_authorization
8
9
  before_action :_authenticate!
9
10
  before_action :set_container_classes
11
+ before_action :add_initial_breadcrumbs
10
12
 
11
13
  rescue_from Pundit::NotAuthorizedError, with: :render_unauthorized
12
14
  rescue_from ActiveRecord::RecordInvalid, with: :exception_logger
@@ -33,6 +35,20 @@ module Avo
33
35
  end
34
36
  end
35
37
 
38
+ def render(*args)
39
+ raise Avo::LicenseTinkeredError, "License verification mechanism tinkered with." unless method(:check_avo_license).source_location.first.match?(/.*\/app\/controllers\/avo\/application_controller\.rb/)
40
+
41
+ super(*args)
42
+ end
43
+
44
+ def check_avo_license
45
+ unless request.original_url.match?(/.*\/avo\/resources\/.*/)
46
+ if !Rails.env.development? && (@license.invalid? || @license.lacks(:custom_tools))
47
+ raise Avo::LicenseInvalidError, "Your license is invalid."
48
+ end
49
+ end
50
+ end
51
+
36
52
  def _current_user
37
53
  instance_eval(&Avo.configuration.current_user)
38
54
  end
@@ -240,5 +256,9 @@ module Avo
240
256
 
241
257
  @container_classes = contain ? "2xl:container 2xl:mx-auto" : ""
242
258
  end
259
+
260
+ def add_initial_breadcrumbs
261
+ instance_eval(&Avo.configuration.initial_breadcrumbs) if Avo.configuration.initial_breadcrumbs.present?
262
+ end
243
263
  end
244
264
  end
@@ -9,6 +9,9 @@ module Avo
9
9
  before_action :set_model, only: [:show, :edit, :destroy, :update]
10
10
 
11
11
  def index
12
+ @page_title = resource_name.humanize
13
+ add_breadcrumb resource_name.humanize
14
+
12
15
  set_index_params
13
16
  set_filters
14
17
  set_actions
@@ -48,15 +51,28 @@ module Avo
48
51
  set_actions
49
52
 
50
53
  @resource = @resource.hydrate(model: @model, view: :show, user: _current_user, params: params)
54
+
55
+ @page_title = @resource.default_panel_name
56
+ add_breadcrumb resource_name.humanize, resources_path(@resource.model_class)
57
+ add_breadcrumb @resource.model_title
51
58
  end
52
59
 
53
60
  def new
54
61
  @model = @resource.model_class.new
55
62
  @resource = @resource.hydrate(model: @model, view: :new, user: _current_user)
63
+
64
+ @page_title = @resource.default_panel_name
65
+ add_breadcrumb resource_name.humanize, resources_path(@resource.model_class)
66
+ add_breadcrumb t("avo.new").humanize
56
67
  end
57
68
 
58
69
  def edit
59
70
  @resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
71
+
72
+ @page_title = @resource.default_panel_name
73
+ add_breadcrumb resource_name.humanize, resources_path(@resource.model_class)
74
+ add_breadcrumb @resource.model_title, resource_path(@resource.model)
75
+ add_breadcrumb t("avo.edit").humanize
60
76
  end
61
77
 
62
78
  def create
@@ -3,7 +3,11 @@ require_dependency "avo/application_controller"
3
3
  module Avo
4
4
  class HomeController < ApplicationController
5
5
  def index
6
- unless Rails.env.development?
6
+ @page_title = "Get started"
7
+
8
+ if Avo.configuration.home_path.present?
9
+ redirect_to Avo.configuration.home_path
10
+ elsif !Rails.env.development?
7
11
  redirect_to resources_path Avo::App.get_resources.min_by { |resource| resource.route_key }.model_class
8
12
  end
9
13
  end
@@ -21,12 +21,6 @@ module Avo
21
21
  }
22
22
  end
23
23
 
24
- def panel(**args, &block)
25
- render(Avo::PanelComponent.new(**args)) do |component|
26
- capture(component, &block)
27
- end
28
- end
29
-
30
24
  def empty_state(resource_name)
31
25
  render partial: "avo/partials/empty_state", locals: {resource_name: resource_name}
32
26
  end
@@ -14,6 +14,7 @@
14
14
  @import './../stylesheets/alerts.css';
15
15
  @import './../stylesheets/loader.css';
16
16
  @import './../stylesheets/pagination.css';
17
+ @import './../stylesheets/breadcrumbs.css';
17
18
 
18
19
  @import './../stylesheets/components/status.css';
19
20
  @import './../stylesheets/components/code.css';
@@ -0,0 +1,12 @@
1
+ .breadcrumbs {
2
+ @apply relative text-sm text-gray-400 font-bold;
3
+
4
+ a,
5
+ span {
6
+ @apply inline-block text-gray-400;
7
+ }
8
+
9
+ a {
10
+ @apply hover:text-gray-500;
11
+ }
12
+ }
@@ -9,3 +9,8 @@
9
9
  height: var(--height) !important;
10
10
  min-height: auto;
11
11
  }
12
+
13
+ /* overlay CodeMirror fullscreen & Preview on small screens */
14
+ .CodeMirror-fullscreen, .editor-preview-active-side {
15
+ z-index: 50;
16
+ }
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
2
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
3
+ </svg>
@@ -9,7 +9,7 @@
9
9
  >
10
10
  <%= form_with model: @model, scope: 'fields', url: "/avo/resources/#{@resource.model_class.model_name.route_key}/actions/#{@action.param_id}", data: {'turbo-frame': '_top', 'action-target': 'form'} do |form| %>
11
11
  <%= render Avo::ModalComponent.new do |c| %>
12
- <% c.with :heading do %>
12
+ <% c.heading do %>
13
13
  <%= @action.name %>
14
14
  <% end %>
15
15
 
@@ -25,7 +25,7 @@
25
25
  <% end %>
26
26
  <% end %>
27
27
 
28
- <% c.with :controls do %>
28
+ <% c.controls do %>
29
29
  <%= a_button @action.cancel_button_label, 'data-action': 'click->modal#close', size: :sm %>
30
30
  <%= a_button @action.confirm_button_label, type: :submit, color: :green, size: :sm %>
31
31
  <% end %>
@@ -1,16 +1,16 @@
1
1
  <div>
2
2
  <h3>Actions</h3>
3
3
 
4
- <div class="mt-2" data-controller="hidden-input">
4
+ <div class="mt-2">
5
5
  Avo actions allow you to perform specific tasks on one or more of your records. For example, you might want to mark a user as inactive and optionally send a message that may be customized by the person that wants to run the action.
6
6
 
7
- <strong class="cursor-pointer block mt-2" data-action="click->hidden-input#showContent">Generate action 👇</strong>
8
-
9
- <div class="mt-2 hidden" data-hidden-input-target="content">
7
+ <div class="mt-2">
10
8
  <code class="text-lg">Toggle published</code>
11
- <div>
9
+ <div class="mt-1">
12
10
  <code class="p-1 rounded bg-light-blue-500 text-white">bin/rails generate avo:action toggle_published</code>
13
11
  </div>
14
12
  </div>
13
+
14
+ <a href="https://docs.avohq.io/1.0/actions.html" target="_blank" title="Avo Actions documentation" class="text-bold cursor-pointer block mt-2">Actions in the docs 👉</a>
15
15
  </div>
16
16
  </div>