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.
- checksums.yaml +4 -4
- data/Gemfile +4 -0
- data/Gemfile.lock +8 -1
- data/app/assets/builds/action_cable.js +2 -0
- data/app/assets/builds/action_cable.js.map +7 -0
- data/app/assets/builds/avo.css +725 -203
- data/app/assets/builds/avo.js +211 -122
- data/app/assets/builds/avo.js.map +3 -3
- data/app/assets/stylesheets/avo.css +2 -0
- data/app/assets/stylesheets/css/tags.css +16 -0
- data/app/components/avo/actions_component.html.erb +1 -1
- data/app/components/avo/button_component.rb +8 -14
- data/app/components/avo/card_component.rb +12 -0
- data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +2 -0
- data/app/components/avo/fields/concerns/item_labels.rb +40 -0
- data/app/components/avo/fields/tags_field/edit_component.html.erb +27 -0
- data/app/components/avo/fields/tags_field/edit_component.rb +4 -0
- data/app/components/avo/fields/tags_field/index_component.html.erb +10 -0
- data/app/components/avo/fields/tags_field/index_component.rb +9 -0
- data/app/components/avo/fields/tags_field/show_component.html.erb +7 -0
- data/app/components/avo/fields/tags_field/show_component.rb +7 -0
- data/app/components/avo/fields/tags_field/tag_component.html.erb +9 -0
- data/app/components/avo/fields/tags_field/tag_component.rb +11 -0
- data/app/components/avo/index/resource_table_component.html.erb +1 -1
- data/app/components/avo/paginator_component.html.erb +2 -2
- data/app/components/avo/sidebar_component.html.erb +1 -1
- data/app/components/avo/sidebar_profile_component.html.erb +1 -1
- data/app/controllers/avo/base_controller.rb +0 -19
- data/app/controllers/avo/search_controller.rb +11 -4
- data/app/javascript/js/application.js +1 -1
- data/app/javascript/js/controllers/base_controller.js +22 -0
- data/app/javascript/js/controllers/fields/tags_field_controller.js +86 -0
- data/app/javascript/js/controllers/fields/tags_field_helpers.js +47 -0
- data/app/javascript/js/controllers/search_controller.js +7 -1
- data/app/javascript/js/controllers.js +2 -0
- data/app/views/avo/dashboards/_chartkick_card.html.erb +1 -1
- data/app/views/avo/dashboards/_metric_card.html.erb +1 -1
- data/app/views/avo/partials/_global_search.html.erb +1 -1
- data/app/views/avo/partials/_navbar.html.erb +3 -3
- data/app/views/avo/partials/_resource_search.html.erb +1 -1
- data/db/factories.rb +2 -0
- data/lib/avo/base_resource.rb +6 -0
- data/lib/avo/concerns/handles_field_args.rb +36 -0
- data/lib/avo/fields/base_field.rb +2 -1
- data/lib/avo/fields/tags_field.rb +82 -0
- data/lib/avo/hosts/association_scope_host.rb +1 -0
- data/lib/avo/hosts/base_host.rb +2 -0
- data/lib/avo/hosts/record_host.rb +7 -0
- data/lib/avo/licensing/pro_license.rb +2 -1
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/cards/chartkick_card.tt +1 -1
- data/lib/generators/avo/templates/cards/chartkick_card_sample.tt +1 -1
- data/lib/generators/avo/templates/cards/metric_card.tt +1 -1
- data/lib/generators/avo/templates/cards/metric_card_sample.tt +1 -1
- data/lib/generators/avo/templates/locales/avo.en.yml +4 -0
- data/lib/tasks/avo_tasks.rake +7 -3
- data/public/avo-assets/avo.css +694 -78
- data/public/avo-assets/avo.js +211 -122
- data/public/avo-assets/avo.js.map +3 -3
- 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';
|
@@ -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-
|
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 +=
|
48
|
-
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
|
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
|
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
|
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-
|
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,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,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 %>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="w-full ">
|
2
|
-
<table class="w-full px-4 bg-white
|
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
|
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">
|
@@ -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?
|
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
|
-
#
|
56
|
-
|
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 =
|
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,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.
|
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]"
|
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
|
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"
|
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
|