avo 2.5.2.pre.6 → 2.6.1.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 +4 -0
  3. data/Gemfile.lock +8 -1
  4. data/app/assets/builds/action_cable.js +2 -0
  5. data/app/assets/builds/action_cable.js.map +7 -0
  6. data/app/assets/builds/avo.css +725 -203
  7. data/app/assets/builds/avo.js +211 -122
  8. data/app/assets/builds/avo.js.map +3 -3
  9. data/app/assets/stylesheets/avo.css +2 -0
  10. data/app/assets/stylesheets/css/tags.css +16 -0
  11. data/app/components/avo/actions_component.html.erb +1 -1
  12. data/app/components/avo/button_component.rb +8 -14
  13. data/app/components/avo/card_component.rb +12 -0
  14. data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +2 -0
  15. data/app/components/avo/fields/concerns/item_labels.rb +40 -0
  16. data/app/components/avo/fields/tags_field/edit_component.html.erb +27 -0
  17. data/app/components/avo/fields/tags_field/edit_component.rb +4 -0
  18. data/app/components/avo/fields/tags_field/index_component.html.erb +10 -0
  19. data/app/components/avo/fields/tags_field/index_component.rb +9 -0
  20. data/app/components/avo/fields/tags_field/show_component.html.erb +7 -0
  21. data/app/components/avo/fields/tags_field/show_component.rb +7 -0
  22. data/app/components/avo/fields/tags_field/tag_component.html.erb +9 -0
  23. data/app/components/avo/fields/tags_field/tag_component.rb +11 -0
  24. data/app/components/avo/index/resource_table_component.html.erb +1 -1
  25. data/app/components/avo/paginator_component.html.erb +2 -2
  26. data/app/components/avo/sidebar_component.html.erb +1 -1
  27. data/app/components/avo/sidebar_profile_component.html.erb +1 -1
  28. data/app/controllers/avo/base_controller.rb +0 -19
  29. data/app/controllers/avo/search_controller.rb +11 -4
  30. data/app/javascript/js/application.js +1 -1
  31. data/app/javascript/js/controllers/base_controller.js +22 -0
  32. data/app/javascript/js/controllers/fields/tags_field_controller.js +86 -0
  33. data/app/javascript/js/controllers/fields/tags_field_helpers.js +47 -0
  34. data/app/javascript/js/controllers/search_controller.js +7 -1
  35. data/app/javascript/js/controllers.js +2 -0
  36. data/app/views/avo/dashboards/_chartkick_card.html.erb +1 -1
  37. data/app/views/avo/dashboards/_metric_card.html.erb +1 -1
  38. data/app/views/avo/partials/_global_search.html.erb +1 -1
  39. data/app/views/avo/partials/_navbar.html.erb +3 -3
  40. data/app/views/avo/partials/_resource_search.html.erb +1 -1
  41. data/db/factories.rb +2 -0
  42. data/lib/avo/base_resource.rb +6 -0
  43. data/lib/avo/concerns/handles_field_args.rb +36 -0
  44. data/lib/avo/fields/base_field.rb +2 -1
  45. data/lib/avo/fields/tags_field.rb +82 -0
  46. data/lib/avo/hosts/association_scope_host.rb +1 -0
  47. data/lib/avo/hosts/base_host.rb +2 -0
  48. data/lib/avo/hosts/record_host.rb +7 -0
  49. data/lib/avo/licensing/pro_license.rb +2 -1
  50. data/lib/avo/version.rb +1 -1
  51. data/lib/generators/avo/templates/cards/chartkick_card.tt +1 -1
  52. data/lib/generators/avo/templates/cards/chartkick_card_sample.tt +1 -1
  53. data/lib/generators/avo/templates/cards/metric_card.tt +1 -1
  54. data/lib/generators/avo/templates/cards/metric_card_sample.tt +1 -1
  55. data/lib/generators/avo/templates/locales/avo.en.yml +4 -0
  56. data/lib/tasks/avo_tasks.rake +7 -3
  57. data/public/avo-assets/avo.css +694 -78
  58. data/public/avo-assets/avo.js +211 -122
  59. data/public/avo-assets/avo.js.map +3 -3
  60. metadata +20 -2
@@ -4,6 +4,7 @@
4
4
  @import './../../../node_modules/trix/dist/trix.css';
5
5
  @import './../../../node_modules/flatpickr/dist/flatpickr.css';
6
6
  @import './../../../node_modules/@algolia/autocomplete-theme-classic/dist/theme.css';
7
+ @import './../../../node_modules/@yaireo/tagify/dist/tagify.css';
7
8
 
8
9
  @import 'tailwindcss/base';
9
10
 
@@ -17,6 +18,7 @@
17
18
  @import './css/search.css';
18
19
  @import './css/active-storage.css';
19
20
  @import './css/spinner.css';
21
+ @import './css/tags.css';
20
22
 
21
23
  @import './css/components/status.css';
22
24
  @import './css/components/code.css';
@@ -0,0 +1,16 @@
1
+ tags.tagify {
2
+ --tag-inset-shadow-size: 3em;
3
+ }
4
+
5
+ tags.tagify {
6
+ @apply !p-0;
7
+
8
+ span.tagify__input {
9
+ @apply my-1;
10
+ }
11
+ }
12
+
13
+ tag.tagify__tag {
14
+ @apply text-sm my-1 mb-0;
15
+ }
16
+
@@ -35,7 +35,7 @@
35
35
  'actions-picker-target': action.standalone ? 'standaloneAction' : 'resourceAction',
36
36
  'disabled': disabled,
37
37
  },
38
- class: "flex items-center px-4 py-4 w-full font-medium #{disabled ? 'text-gray-500' : 'text-black hover:bg-blue-500 hover:text-white'}" do %>
38
+ class: "flex items-center px-4 py-3 w-full font-semibold text-sm #{disabled ? 'text-gray-500' : 'text-black hover:bg-blue-500 hover:text-white'}" do %>
39
39
  <%= svg 'play', class: 'h-5 mr-1 inline' %> <%= action.action_name %>
40
40
  <% end %>
41
41
  <% end %>
@@ -44,8 +44,8 @@ class Avo::ButtonComponent < ViewComponent::Base
44
44
 
45
45
  classes += style_classes
46
46
 
47
- classes += padding_classes
48
- classes += spacing_classes
47
+ classes += horizontal_padding_classes
48
+ classes += vertical_padding_classes
49
49
  classes += text_size_classes
50
50
 
51
51
  classes
@@ -101,7 +101,7 @@ class Avo::ButtonComponent < ViewComponent::Base
101
101
 
102
102
  private
103
103
 
104
- def spacing_classes
104
+ def vertical_padding_classes
105
105
  case @size.to_sym
106
106
  when :xs
107
107
  " py-0"
@@ -118,7 +118,7 @@ class Avo::ButtonComponent < ViewComponent::Base
118
118
  end
119
119
  end
120
120
 
121
- def padding_classes
121
+ def horizontal_padding_classes
122
122
  return " px-1" if @compact
123
123
 
124
124
  case @size.to_sym
@@ -164,19 +164,13 @@ class Avo::ButtonComponent < ViewComponent::Base
164
164
 
165
165
  case @size
166
166
  when :xs
167
- icon_classes += " h-4"
168
- # When icon is solo we need to add an offset
169
- icon_classes += " my-1" if content.blank?
167
+ icon_classes += " h-4 my-1"
170
168
  when :sm
171
- icon_classes += " h-4"
172
- # When icon is solo we need to add an offset
173
- icon_classes += " my-1" if content.blank?
169
+ icon_classes += " h-4 my-1"
174
170
  when :md
175
- icon_classes += " h-4 my-1.5"
176
- # When icon is solo we need to add an offset
177
- icon_classes += " my-0.5" if content.blank?
171
+ icon_classes += " h-4 my-1"
178
172
  when :lg
179
- icon_classes += " h-5 my-2"
173
+ icon_classes += " h-5 my-0.5"
180
174
  when :xl
181
175
  icon_classes += " h-6"
182
176
  end
@@ -3,9 +3,21 @@
3
3
  class Avo::CardComponent < ViewComponent::Base
4
4
  def initialize(card: nil)
5
5
  @card = card
6
+
7
+ init_card
6
8
  end
7
9
 
8
10
  def render?
9
11
  !@card.nil?
10
12
  end
13
+
14
+ # Initializing the card byt running the query method.
15
+ # We'll still keep the query block around for compatibility reasons.
16
+ def init_card
17
+ if @card.respond_to? :query
18
+ @card.query
19
+ elsif @card.query_block.present?
20
+ @card.compute_result
21
+ end
22
+ end
11
23
  end
@@ -7,6 +7,8 @@
7
7
  data-via-association-id="<%= @field.id %>"
8
8
  data-via-reflection-id="<%= @field.model.id %>"
9
9
  data-via-reflection-class="<%= @field.model.class.to_s %>"
10
+ data-via-parent-resource-id="<%= params[:via_resource_id] %>"
11
+ data-via-parent-resource-class="<%= params[:via_relation_class] %>"
10
12
  ></div>
11
13
  <div class="relative w-full" autocomplete="off">
12
14
  <%= @form.text_field @foreign_key,
@@ -0,0 +1,40 @@
1
+ module Avo
2
+ module Fields
3
+ module Concerns
4
+ module ItemLabels
5
+ extend ActiveSupport::Concern
6
+
7
+ def value_for_item(item)
8
+ if @field.acts_as_taggable_on.present?
9
+ item["value"]
10
+ else
11
+ item
12
+ end
13
+ end
14
+
15
+ def label_from_item(item)
16
+ value = value_for_item item
17
+
18
+ if suggestions_are_a_hash?
19
+ return suggestions_by_id[value.to_s][:label] if suggestions_by_id[value.to_s].present?
20
+ end
21
+ value
22
+ end
23
+
24
+ def suggestions_by_id
25
+ return {} unless suggestions_are_a_hash?
26
+
27
+ @field.suggestions.map do |suggestion|
28
+ [suggestion[:value].to_s, suggestion]
29
+ end.to_h
30
+ end
31
+
32
+ def suggestions_are_a_hash?
33
+ return false if @field.suggestions.blank?
34
+
35
+ @field.suggestions.first.is_a? Hash
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
2
+ <div data-controller="tags-field">
3
+ <%# dummy field %>
4
+ <%= text_field_tag "#{@field.id}-dummy", '',
5
+ class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
6
+ placeholder: @field.placeholder,
7
+ disabled: @field.readonly,
8
+ value: '',
9
+ data: {
10
+ 'tags-field-target': 'fakeInput',
11
+ } %>
12
+ <%# real field %>
13
+ <%= @form.text_field @field.id,
14
+ class: helpers.input_classes('hidden w-full', has_error: @field.model_errors.include?(@field.id)),
15
+ placeholder: @field.placeholder,
16
+ disabled: @field.readonly,
17
+ value: @field.field_value.to_json,
18
+ data: {
19
+ 'tags-field-target': 'input',
20
+ 'whitelist-items': @field.suggestions.to_json,
21
+ 'disallowed-items': @field.disallowed.to_json,
22
+ 'enforce-suggestions': @field.enforce_suggestions ? 1 : 0,
23
+ 'delimiters': @field.delimiters,
24
+ 'close-on-select': @field.close_on_select ? 1 : 0,
25
+ } %>
26
+ </div>
27
+ <% end %>
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Fields::TagsField::EditComponent < Avo::Fields::EditComponent
4
+ end
@@ -0,0 +1,10 @@
1
+ <%= index_field_wrapper field: @field do %>
2
+ <div class="flex gap-1 items-center flex-nowrap">
3
+ <% value.take(3).each do |item| %>
4
+ <%= render Avo::Fields::TagsField::TagComponent.new(label: label_from_item(item)) %>
5
+ <% end %>
6
+ <% if value.count > 3 %>
7
+ <%= render Avo::Fields::TagsField::TagComponent.new(label: '...', title: I18n.t('avo.x_items_more', count: value.count - 3)) %>
8
+ <% end %>
9
+ </div>
10
+ <% end %>
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Fields::TagsField::IndexComponent < Avo::Fields::IndexComponent
4
+ include Avo::Fields::Concerns::ItemLabels
5
+
6
+ def value
7
+ @field.field_value
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ <%= show_field_wrapper field: @field, index: @index do %>
2
+ <div class="flex gap-1 items-center flex-wrap">
3
+ <% @field.field_value.each do |item| %>
4
+ <%= render Avo::Fields::TagsField::TagComponent.new(label: label_from_item(item)) %>
5
+ <% end %>
6
+ </div>
7
+ <% end %>
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require_relative 'item_labels'
4
+
5
+ class Avo::Fields::TagsField::ShowComponent < Avo::Fields::ShowComponent
6
+ include Avo::Fields::Concerns::ItemLabels
7
+ end
@@ -0,0 +1,9 @@
1
+ <div class="flex px-2 py-1 rounded bg-gray-100 text-sm text-gray-800 font-normal flex-shrink-0"
2
+ <% if title.present? %>
3
+ data-tippy="tooltip"
4
+ title="<%= title %>"
5
+ <% end %>
6
+ data-target="tag-component"
7
+ >
8
+ <%= label %>
9
+ </div>
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Fields::TagsField::TagComponent < ViewComponent::Base
4
+ attr_reader :label
5
+ attr_reader :title
6
+
7
+ def initialize(label: nil, title: nil)
8
+ @label = label
9
+ @title = title
10
+ end
11
+ end
@@ -1,5 +1,5 @@
1
1
  <div class="w-full ">
2
- <table class="w-full px-4 bg-white rounded" data-resource-name='<%= @resource.model_key %>' data-controller='item-select-all'>
2
+ <table class="w-full px-4 bg-white" data-resource-name='<%= @resource.model_key %>' data-controller='item-select-all'>
3
3
  <%= render partial: 'avo/partials/table_header', locals: {fields: @resource.get_fields(reflection: @reflection)} %>
4
4
  <tbody class="divide-y">
5
5
  <% @resources.each_with_index do |resource, index| %>
@@ -25,9 +25,9 @@
25
25
  </div>
26
26
  <% per_page_options.each do |option| %>
27
27
  <% if parent_model.present? %>
28
- <%= link_to "Change to #{option} items per page", helpers.related_resources_path(parent_model, parent_model, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
28
+ <%= link_to "Change to #{option} items per page", helpers.related_resources_path(parent_model, parent_model, per_page: option, keep_query_params: true, page: 1), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
29
29
  <% else %>
30
- <%= link_to "Change to #{option} items per page", helpers.resources_path(resource: resource, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
30
+ <%= link_to "Change to #{option} items per page", helpers.resources_path(resource: resource, per_page: option, keep_query_params: true, page: 1), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
31
31
  <% end %>
32
32
  <% end %>
33
33
  </div>
@@ -1,5 +1,5 @@
1
1
  <div
2
- class="fixed z-[60] t-0 application-sidebar hidden lg:flex flex-1 border-r lg:border-none bg-none min-w-[16rem] h-[calc(100vh-4rem)] bg-gray-25 lg:bg-transparent <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>"
2
+ class="fixed z-[60] t-0 application-sidebar w-64 hidden lg:flex flex-1 border-r lg:border-none bg-none h-[calc(100vh-4rem)] bg-gray-25 lg:bg-transparent <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>"
3
3
  data-mobile-target="sidebar"
4
4
  >
5
5
  <div class="flex flex-col w-full h-full">
@@ -1,4 +1,4 @@
1
- <div class="text-black Xborder-t border-gray-200 p-4 flex">
1
+ <div class="text-black border-gray-200 p-4 flex">
2
2
  <div class="flex-1 flex space-x-4">
3
3
  <% if avatar.present? %>
4
4
  <div class="relative aspect-square w-10 h-10 overflow-hidden rounded">
@@ -11,8 +11,6 @@ module Avo
11
11
  before_action :set_edit_title_and_breadcrumbs, only: [:edit, :update]
12
12
  before_action :fill_model, only: [:create, :update]
13
13
  before_action :authorize_action
14
- # before_action :reset_pagination_if_filters_changed, only: :index
15
- # before_action :cache_applied_filters, only: :index
16
14
 
17
15
  def index
18
16
  @page_title = @resource.plural_name.humanize
@@ -360,23 +358,6 @@ module Avo
360
358
  filter_defaults.merge(@applied_filters)
361
359
  end
362
360
 
363
- # Caching these so we know when the filters have changed so we reset the pagination
364
- # def cache_applied_filters
365
- # # puts ["Rails.session->", session].inspect
366
- # session[:avo_applied_filters] = params[:filters]
367
- # # ::Avo::App.cache_store.delete(applied_filters_cache_key) if params[:filters].nil?
368
-
369
- # # ::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in: 1.day)
370
- # end
371
-
372
- # def reset_pagination_if_filters_changed
373
- # params[:page] = 1 if params[:filters] != session[:avo_applied_filters]
374
- # end
375
-
376
- # def applied_filters_cache_key
377
- # "avo.base_controller.#{@resource.model_key}.applied_filters"
378
- # end
379
-
380
361
  def set_edit_title_and_breadcrumbs
381
362
  @resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
382
363
  @page_title = @resource.default_panel_name.to_s
@@ -47,16 +47,23 @@ module Avo
47
47
  query = resource.search_query.call(params: params).limit(8)
48
48
 
49
49
  # Figure oute if this is a belongs_to search
50
- if params[:via_reflection_class].present? && params[:via_reflection_id].present?
50
+ if params[:via_reflection_class].present?
51
51
  # Fetch the field
52
52
  field = belongs_to_field
53
53
 
54
54
  if field.attach_scope.present?
55
- # Fetch the parent
56
- parent = params[:via_reflection_class].safe_constantize.find params[:via_reflection_id]
55
+ # Try to fetch the parent.
56
+ if params[:via_reflection_id].present?
57
+ parent = params[:via_reflection_class].safe_constantize.find params[:via_reflection_id]
58
+ end
59
+
60
+ # Try to fetch the grandparent for the new views where the parent is nil.
61
+ if params[:via_parent_resource_id].present? && params[:via_parent_resource_class].present?
62
+ grandparent = params[:via_parent_resource_class].safe_constantize.find params[:via_parent_resource_id]
63
+ end
57
64
 
58
65
  # Add to the query
59
- query = Avo::Hosts::AssociationScopeHost.new(block: belongs_to_field.attach_scope, query: query, parent: parent).handle
66
+ query = Avo::Hosts::AssociationScopeHost.new(block: belongs_to_field.attach_scope, query: query, parent: parent, grandparent: grandparent).handle
60
67
  end
61
68
  end
62
69
 
@@ -4,7 +4,7 @@ import { Application } from '@hotwired/stimulus'
4
4
  const application = Application.start()
5
5
 
6
6
  // Configure Stimulus development experience
7
- application.debug = false
7
+ application.debug = window?.localStorage.getItem('avo.debug')
8
8
  window.Stimulus = application
9
9
 
10
10
  // Register stimulus-components controller
@@ -0,0 +1,22 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ /**
5
+ * Helper that parses the data attribute value to JSON
6
+ */
7
+ getJsonAttribute(target, attribute, defaultValue = []) {
8
+ let result = defaultValue
9
+ try {
10
+ result = JSON.parse(target.getAttribute(attribute))
11
+ } catch (error) {}
12
+
13
+ return result
14
+ }
15
+
16
+ /**
17
+ * Parses the attribute to boolean
18
+ */
19
+ getBooleanAttribute(target, attribute) {
20
+ return target.getAttribute(attribute) === '1'
21
+ }
22
+ }
@@ -0,0 +1,86 @@
1
+ import { first, isObject, merge } from 'lodash'
2
+ import Tagify from '@yaireo/tagify'
3
+
4
+ import BaseController from '../base_controller'
5
+
6
+ import { suggestionItemTemplate, tagTemplate } from './tags_field_helpers'
7
+
8
+ export default class extends BaseController {
9
+ static targets = ['input', 'fakeInput'];
10
+
11
+ tagify = null;
12
+
13
+ get whitelistItems() {
14
+ return this.getJsonAttribute(this.inputTarget, 'data-whitelist-items', [])
15
+ }
16
+
17
+ get disallowedItems() {
18
+ return this.getJsonAttribute(this.inputTarget, 'data-disallowed-items', [])
19
+ }
20
+
21
+ get enforceSuggestions() {
22
+ return this.getBooleanAttribute(this.inputTarget, 'data-enforce-suggestions')
23
+ }
24
+
25
+ get closeOnSelect() {
26
+ return this.getBooleanAttribute(this.inputTarget, 'data-close-on-select')
27
+ }
28
+
29
+ get delimiters() {
30
+ return this.getJsonAttribute(this.inputTarget, 'data-delimiters', [])
31
+ }
32
+
33
+ get suggestionsAreObjects() {
34
+ return isObject(first(this.whitelistItems))
35
+ }
36
+
37
+ get tagifyOptions() {
38
+ let options = {
39
+ whitelist: this.whitelistItems,
40
+ blacklist: this.disallowedItems,
41
+ enforceWhitelist: this.enforceSuggestions,
42
+ delimiters: this.delimiters.join('|'),
43
+ maxTags: 10,
44
+ dropdown: {
45
+ maxItems: 20,
46
+ enabled: 0,
47
+ closeOnSelect: this.closeOnSelect,
48
+ },
49
+ }
50
+
51
+ if (this.suggestionsAreObjects) {
52
+ options = merge(options, {
53
+ tagTextProp: 'label',
54
+ dropdown: {
55
+ searchKeys: ['label'],
56
+ },
57
+ templates: {
58
+ tag: tagTemplate,
59
+ dropdownItem: suggestionItemTemplate,
60
+ },
61
+ })
62
+ }
63
+
64
+ return options
65
+ }
66
+
67
+ connect() {
68
+ if (this.hasInputTarget) {
69
+ this.hideFakeInput()
70
+ this.showRealInput()
71
+ this.initTagify()
72
+ }
73
+ }
74
+
75
+ initTagify() {
76
+ this.tagify = new Tagify(this.inputTarget, this.tagifyOptions)
77
+ }
78
+
79
+ hideFakeInput() {
80
+ this.fakeInputTarget.classList.add('hidden')
81
+ }
82
+
83
+ showRealInput() {
84
+ this.inputTarget.classList.remove('hidden')
85
+ }
86
+ }
@@ -0,0 +1,47 @@
1
+ export function tagTemplate(tagData) {
2
+ const suggestions = this.settings.whitelist || []
3
+
4
+ const possibleSuggestion = suggestions.find(
5
+ // eslint-disable-next-line eqeqeq
6
+ (item) => item.value == tagData.value,
7
+ )
8
+ const possibleLabel = possibleSuggestion
9
+ ? possibleSuggestion.label
10
+ : tagData.value
11
+
12
+ return `
13
+ <tag title="${tagData.value}"
14
+ contenteditable='false'
15
+ spellcheck='false'
16
+ tabIndex="-1"
17
+ class="tagify__tag ${tagData.class ? tagData.class : ''}"
18
+ ${this.getAttributes(tagData)}
19
+ >
20
+ <x title='' class='tagify__tag__removeBtn' role='button' aria-label='remove tag'></x>
21
+ <div>
22
+ <span class='tagify__tag-text'>${possibleLabel}</span>
23
+ </div>
24
+ </tag>
25
+ `
26
+ }
27
+
28
+ export function suggestionItemTemplate(tagData) {
29
+ return `
30
+ <div ${this.getAttributes(tagData)}
31
+ class='tagify__dropdown__item flex items-center ${
32
+ tagData.class ? tagData.class : ''
33
+ }'
34
+ tabindex="0"
35
+ role="option">
36
+ ${
37
+ tagData.avatar
38
+ ? `
39
+ <div class='rounded w-8 h-8 block mr-2'>
40
+ <img onerror="this.style.visibility='hidden'" class="w-full" src="${tagData.avatar}">
41
+ </div>`
42
+ : ''
43
+ }
44
+ <span>${tagData.label}</span>
45
+ </div>
46
+ `
47
+ }
@@ -76,6 +76,10 @@ export default class extends Controller {
76
76
  via_reflection_class: this.dataset.viaReflectionClass,
77
77
  // eslint-disable-next-line camelcase
78
78
  via_reflection_id: this.dataset.viaReflectionId,
79
+ // eslint-disable-next-line camelcase
80
+ via_parent_resource_id: this.dataset.viaParentResourceId,
81
+ // eslint-disable-next-line camelcase
82
+ via_parent_resource_class: this.dataset.viaParentResourceClass,
79
83
  }
80
84
  }
81
85
 
@@ -193,7 +197,7 @@ export default class extends Controller {
193
197
  Mousetrap.bind(['command+k', 'ctrl+k'], () => this.showSearchPanel())
194
198
  }
195
199
 
196
- autocomplete({
200
+ const { destroy } = autocomplete({
197
201
  container: this.autocompleteTarget,
198
202
  placeholder: this.translationKeys.placeholder,
199
203
  translations: {
@@ -216,6 +220,8 @@ export default class extends Controller {
216
220
  },
217
221
  })
218
222
 
223
+ document.addEventListener('turbo:before-render', destroy)
224
+
219
225
  // When using search for belongs-to
220
226
  if (this.buttonTarget.dataset.shouldBeDisabled !== 'true') {
221
227
  this.buttonTarget.removeAttribute('disabled')
@@ -24,6 +24,7 @@ import SearchController from './controllers/search_controller'
24
24
  import SelectController from './controllers/select_controller'
25
25
  import SelectFilterController from './controllers/select_filter_controller'
26
26
  import SimpleMdeController from './controllers/fields/simple_mde_controller'
27
+ import TagsFieldController from './controllers/fields/tags_field_controller'
27
28
  import TextFilterController from './controllers/text_filter_controller'
28
29
  import TippyController from './controllers/tippy_controller'
29
30
  import TogglePanelController from './controllers/toggle_panel_controller'
@@ -48,6 +49,7 @@ application.register('per-page', PerPageController)
48
49
  application.register('search', SearchController)
49
50
  application.register('select', SelectController)
50
51
  application.register('select-filter', SelectFilterController)
52
+ application.register('tags-field', TagsFieldController)
51
53
  application.register('text-filter', TextFilterController)
52
54
  application.register('tippy', TippyController)
53
55
  application.register('toggle-panel', TogglePanelController)
@@ -1,3 +1,3 @@
1
1
  <div class="relative flex flex-col <%= @card.chartkick_classes %>">
2
- <%= send(@card.chart_type, @card.compute_result.result_data, **@card.chartkick_options) %>
2
+ <%= send(@card.chart_type, @card.result_data, **@card.chartkick_options) %>
3
3
  </div>
@@ -1,5 +1,5 @@
1
1
  <div class="flex mt-4 items-end">
2
2
  <span class="text-3xl"><%= @card.prefix %></span>
3
- <span class="text-5xl"><%= @card.compute_result.result_data %></span>
3
+ <span class="text-5xl"><%= @card.result_data %></span>
4
4
  <span class="text-3xl"><%= @card.suffix %></span>
5
5
  </div>
@@ -1,4 +1,4 @@
1
- <div data-controller="search" class="relative flex global-search rounded border border-gray-200 sm:min-w-[16rem]" data-turbo-remove-before-cache>
1
+ <div data-controller="search" class="relative flex global-search rounded border border-gray-200 sm:min-w-[16rem]">
2
2
  <div class="flex-1 text-gray-500"
3
3
  data-search-target="autocomplete"
4
4
  data-search-resource="global"
@@ -1,12 +1,12 @@
1
1
  <div
2
- class="fixed bg-white p-2 w-full flex flex-shrink-0 items-center z-50 px-4 lg:px-4 border-b space-x-4 lg:space-x-0 min-h-[4rem] <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>"
2
+ class="fixed bg-white p-2 w-full flex flex-shrink-0 items-center z-50 px-4 lg:px-4 border-b space-x-4 lg:space-x-0 h-16 <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>"
3
3
  v-if="layout !== 'blank'"
4
4
  >
5
- <div class="flex items-center space-x-2 lg:space-x-0 w-64">
5
+ <div class="flex items-center space-x-2 lg:space-x-0 w-32 sm:w-64 flex-shrink-0">
6
6
  <%= a_button class: 'lg:hidden', icon: 'menu', size: :xs, compact: true, style: :text, data: { action: 'click->mobile#toggleSidebar' } %>
7
7
  <%= render partial: "avo/partials/logo" %>
8
8
  </div>
9
- <div class="flex-1 flex items-center justify-between lg:justify-start space-x-8 lg:pl-4">
9
+ <div class="flex-1 flex items-center justify-between lg:justify-start space-x-2 sm:space-x-8 lg:pl-4">
10
10
  <div class="flex">
11
11
  <%= render partial: "avo/partials/global_search" if ::Avo::App.license.has_with_trial(:global_search) && ::Avo.configuration.feature_enabled?(:global_search) %>
12
12
  </div>
@@ -1,4 +1,4 @@
1
- <div data-controller="search" class="resource-search flex items-center h-full w-full" data-turbo-remove-before-cache>
1
+ <div data-controller="search" class="resource-search flex items-center h-full w-full">
2
2
  <div class="w-full"
3
3
  data-search-target="autocomplete"
4
4
  data-search-resource="<%= resource %>"
data/db/factories.rb CHANGED
@@ -25,6 +25,7 @@ FactoryBot.define do
25
25
  Time.now - rand(10...365).days
26
26
  end
27
27
  end
28
+ tag_list { ["1", "2", "five", "seven"].shuffle }
28
29
  status { ::Post.statuses.keys.sample }
29
30
  end
30
31
 
@@ -64,6 +65,7 @@ FactoryBot.define do
64
65
 
65
66
  factory :course do
66
67
  name { Faker::Educator.unique.course_name }
68
+ skills { [Faker::Educator.subject, Faker::Educator.subject, Faker::Educator.subject, Faker::Educator.subject, Faker::Educator.subject] }
67
69
  country { Course.countries.sample }
68
70
  city { Course.cities.stringify_keys[country].sample }
69
71
  end
@@ -136,6 +136,12 @@ module Avo
136
136
  end
137
137
  end
138
138
 
139
+ if Avo::App.license.lacks_with_trial(:advanced_fields)
140
+ fields = fields.reject do |field|
141
+ field.type == 'tags'
142
+ end
143
+ end
144
+
139
145
  fields
140
146
  end
141
147