avo 2.5.2.pre.4 → 2.5.2.pre.7
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 +2 -0
- data/Gemfile.lock +4 -1
- data/app/assets/builds/avo.css +764 -166
- data/app/assets/builds/avo.js +212 -123
- data/app/assets/builds/avo.js.map +3 -3
- data/app/assets/stylesheets/avo.css +3 -33
- data/app/assets/stylesheets/css/alerts.css +35 -0
- data/app/assets/stylesheets/css/search.css +1 -1
- data/app/assets/stylesheets/css/tags.css +16 -0
- data/app/assets/svgs/heroicons/solid/user-remove.svg +1 -1
- data/app/components/avo/actions_component.html.erb +1 -2
- data/app/components/avo/alert_component.html.erb +1 -1
- data/app/components/avo/alert_component.rb +5 -24
- data/app/components/avo/button_component.rb +0 -2
- 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 +14 -0
- data/app/components/avo/fields/tags_field/index_component.rb +7 -0
- data/app/components/avo/fields/tags_field/show_component.html.erb +7 -0
- data/app/components/avo/fields/tags_field/show_component.rb +11 -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/filters_component.html.erb +1 -1
- data/app/components/avo/index/field_wrapper_component.html.erb +1 -1
- data/app/components/avo/index/grid_cover_empty_state_component.html.erb +1 -1
- data/app/components/avo/index/resource_table_component.html.erb +1 -1
- data/app/components/avo/panel_component.html.erb +3 -3
- data/app/components/avo/panel_component.rb +1 -1
- data/app/components/avo/resource_component.rb +0 -50
- data/app/components/avo/sidebar/heading_component.html.erb +1 -1
- data/app/components/avo/sidebar/link_component.rb +1 -1
- data/app/components/avo/sidebar_component.html.erb +13 -5
- data/app/components/avo/sidebar_profile_component.html.erb +1 -1
- data/app/components/avo/views/resource_edit_component.html.erb +3 -28
- data/app/components/avo/views/resource_edit_component.rb +6 -4
- data/app/components/avo/views/resource_index_component.html.erb +7 -15
- data/app/components/avo/views/resource_new_component.html.erb +2 -8
- data/app/components/avo/views/resource_show_component.html.erb +5 -15
- data/app/components/avo/views/resource_show_component.rb +45 -0
- data/app/controllers/avo/actions_controller.rb +8 -23
- data/app/controllers/avo/associations_controller.rb +3 -3
- data/app/controllers/avo/base_controller.rb +16 -25
- data/app/controllers/avo/search_controller.rb +2 -2
- data/app/helpers/avo/application_helper.rb +1 -1
- data/app/javascript/js/application.js +1 -1
- data/app/javascript/js/controllers/alerts_controller.js +26 -0
- data/app/javascript/js/controllers/base_controller.js +22 -0
- data/app/javascript/js/controllers/fields/key_value_controller.js +1 -1
- 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/filter_controller.js +1 -4
- data/app/javascript/js/controllers.js +4 -0
- data/app/views/avo/actions/show.html.erb +2 -5
- data/app/views/avo/partials/_logo.html.erb +1 -1
- data/app/views/avo/partials/_navbar.html.erb +6 -12
- data/app/views/avo/private/_links_and_buttons.html.erb +1 -1
- data/app/views/layouts/avo/application.html.erb +53 -50
- data/db/factories.rb +2 -0
- data/lib/avo/base_action.rb +6 -24
- data/lib/avo/base_resource.rb +6 -0
- data/lib/avo/concerns/handles_field_args.rb +36 -0
- data/lib/avo/engine.rb +1 -1
- data/lib/avo/fields/base_field.rb +2 -1
- data/lib/avo/fields/belongs_to_field.rb +4 -4
- data/lib/avo/fields/has_and_belongs_to_many_field.rb +2 -2
- data/lib/avo/fields/has_base_field.rb +0 -2
- data/lib/avo/fields/has_many_field.rb +2 -2
- data/lib/avo/fields/has_one_field.rb +2 -2
- data/lib/avo/fields/tags_field.rb +82 -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/locales/avo.en.yml +4 -0
- data/public/avo-assets/avo.css +812 -214
- data/public/avo-assets/avo.js +212 -123
- data/public/avo-assets/avo.js.map +3 -3
- metadata +19 -2
@@ -8,38 +8,32 @@
|
|
8
8
|
<% c.tools do %>
|
9
9
|
<% if resource_panel[:name] == @resource.default_panel_name %>
|
10
10
|
<% if @reflection.present? && @resource.model.present? %>
|
11
|
+
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
|
11
12
|
<% if can_detach? %>
|
12
13
|
<%= a_button url: detach_path,
|
13
14
|
icon: 'detach',
|
14
15
|
method: :delete,
|
15
16
|
form_class: 'flex flex-col sm:flex-row sm:inline-flex',
|
16
|
-
style: :text,
|
17
17
|
data: {
|
18
18
|
confirm: "Are you sure you want to detach this #{@reflection.name.to_s}."
|
19
19
|
} do %>
|
20
20
|
<%= t('avo.detach_item', item: @reflection.name.to_s).capitalize %>
|
21
21
|
<% end %>
|
22
|
-
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
|
23
22
|
<% end %>
|
24
23
|
<% if can_see_the_edit_button? %>
|
25
|
-
<%= a_link edit_path,
|
26
|
-
color: :primary,
|
27
|
-
style: :primary,
|
28
|
-
icon: 'edit' do %>
|
24
|
+
<%= a_link edit_path, color: :primary, icon: 'edit' do %>
|
29
25
|
<%= t('avo.edit').capitalize %>
|
30
26
|
<% end %>
|
31
27
|
<% end %>
|
32
28
|
<% else %>
|
33
|
-
<%= a_link back_path,
|
34
|
-
style: :text,
|
35
|
-
icon: 'arrow-left' do %>
|
29
|
+
<%= a_link back_path, icon: 'arrow-left' do %>
|
36
30
|
<%= t('avo.go_back') %>
|
37
31
|
<% end %>
|
32
|
+
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
|
38
33
|
<% if can_see_the_destroy_button? %>
|
39
34
|
<%= a_button url: helpers.resource_path(model: @resource.model, resource: @resource),
|
40
35
|
method: :delete,
|
41
36
|
local: true,
|
42
|
-
style: :text,
|
43
37
|
title: t('avo.delete_item', item: @resource.model.model_name.name.downcase).capitalize,
|
44
38
|
loading: true,
|
45
39
|
confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
|
@@ -54,12 +48,8 @@
|
|
54
48
|
<%= t('avo.delete').capitalize %>
|
55
49
|
<% end %>
|
56
50
|
<% end %>
|
57
|
-
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
|
58
51
|
<% if @resource.authorization.authorize_action(:edit, raise_exception: false) %>
|
59
|
-
<%= a_link edit_path,
|
60
|
-
color: :primary,
|
61
|
-
style: :primary,
|
62
|
-
icon: 'edit' do %>
|
52
|
+
<%= a_link edit_path, color: :primary, icon: 'edit' do %>
|
63
53
|
<%= t('avo.edit').capitalize %>
|
64
54
|
<% end %>
|
65
55
|
<% end %>
|
@@ -44,8 +44,53 @@ class Avo::Views::ResourceShowComponent < Avo::ResourceComponent
|
|
44
44
|
helpers.edit_resource_path(model: @resource.model, resource: @resource, **args)
|
45
45
|
end
|
46
46
|
|
47
|
+
def detach_path
|
48
|
+
helpers.resource_detach_path(params[:resource_name], params[:id], @reflection.name.to_s, @resource.model.id)
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroy_path
|
52
|
+
helpers.resource_path(model: @resource.model, resource: @resource)
|
53
|
+
end
|
54
|
+
|
55
|
+
def can_detach?
|
56
|
+
authorize_association_for("detach")
|
57
|
+
end
|
58
|
+
|
59
|
+
def can_see_the_edit_button?
|
60
|
+
@resource.authorization.authorize_action(:edit, raise_exception: false)
|
61
|
+
end
|
62
|
+
|
63
|
+
def can_see_the_destroy_button?
|
64
|
+
@resource.authorization.authorize_action(:destroy, raise_exception: false)
|
65
|
+
end
|
66
|
+
|
47
67
|
private
|
48
68
|
|
69
|
+
def via_resource?
|
70
|
+
params[:via_resource_class].present? && params[:via_resource_id].present?
|
71
|
+
end
|
72
|
+
|
73
|
+
def split_panel_fields
|
74
|
+
@fields_by_panel = {}
|
75
|
+
@has_one_panels = []
|
76
|
+
@has_many_panels = []
|
77
|
+
@has_as_belongs_to_many_panels = []
|
78
|
+
|
79
|
+
@resource.get_fields.each do |field|
|
80
|
+
case field.class.to_s
|
81
|
+
when "Avo::Fields::HasOneField"
|
82
|
+
@has_one_panels << field
|
83
|
+
when "Avo::Fields::HasManyField"
|
84
|
+
@has_many_panels << field
|
85
|
+
when "Avo::Fields::HasAndBelongsToManyField"
|
86
|
+
@has_as_belongs_to_many_panels << field
|
87
|
+
else
|
88
|
+
@fields_by_panel[field.panel_name] ||= []
|
89
|
+
@fields_by_panel[field.panel_name] << field
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
49
94
|
# In development and test environments we shoudl show the invalid field errors
|
50
95
|
def should_display_invalid_fields_errors?
|
51
96
|
(Rails.env.development? || Rails.env.test?) && @resource.invalid_fields.present?
|
@@ -14,12 +14,14 @@ module Avo
|
|
14
14
|
resource_ids = action_params[:fields][:resource_ids].split(",")
|
15
15
|
models = @resource.class.find_scope.find resource_ids
|
16
16
|
|
17
|
-
fields = action_params[:fields].
|
17
|
+
fields = action_params[:fields].select do |key, value|
|
18
|
+
key != "resource_ids"
|
19
|
+
end
|
18
20
|
|
19
21
|
args = {
|
20
22
|
fields: fields,
|
21
23
|
current_user: _current_user,
|
22
|
-
resource: resource
|
24
|
+
resource: resource,
|
23
25
|
}
|
24
26
|
|
25
27
|
args[:models] = models unless @action.standalone
|
@@ -47,7 +49,8 @@ module Avo
|
|
47
49
|
|
48
50
|
def respond(response)
|
49
51
|
response[:type] ||= :reload
|
50
|
-
|
52
|
+
response[:message_type] ||= :notice
|
53
|
+
response[:message] ||= I18n.t("avo.action_ran_successfully")
|
51
54
|
|
52
55
|
if response[:type] == :download
|
53
56
|
return send_data response[:path], filename: response[:filename]
|
@@ -55,11 +58,6 @@ module Avo
|
|
55
58
|
|
56
59
|
respond_to do |format|
|
57
60
|
format.html do
|
58
|
-
# Flash the messages collected from the action
|
59
|
-
messages.each do |message|
|
60
|
-
flash[message[:type]] = message[:body]
|
61
|
-
end
|
62
|
-
|
63
61
|
if response[:type] == :redirect
|
64
62
|
path = response[:path]
|
65
63
|
|
@@ -67,25 +65,12 @@ module Avo
|
|
67
65
|
path = instance_eval(&path)
|
68
66
|
end
|
69
67
|
|
70
|
-
redirect_to path
|
68
|
+
redirect_to path, "#{response[:message_type]}": response[:message]
|
71
69
|
elsif response[:type] == :reload
|
72
|
-
redirect_back fallback_location: resources_path(resource: @resource)
|
70
|
+
redirect_back fallback_location: resources_path(resource: @resource), "#{response[:message_type]}": response[:message]
|
73
71
|
end
|
74
72
|
end
|
75
73
|
end
|
76
74
|
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
def get_messages(response)
|
81
|
-
default_message = {
|
82
|
-
type: :info,
|
83
|
-
body: I18n.t("avo.action_ran_successfully")
|
84
|
-
}
|
85
|
-
|
86
|
-
return [default_message] if response[:messages].blank?
|
87
|
-
|
88
|
-
response[:messages]
|
89
|
-
end
|
90
75
|
end
|
91
76
|
end
|
@@ -46,8 +46,8 @@ module Avo
|
|
46
46
|
query = @authorization.apply_policy @attachment_class
|
47
47
|
|
48
48
|
# Add the association scope to the query scope
|
49
|
-
if @field.
|
50
|
-
query = Avo::Hosts::AssociationScopeHost.new(block: @field.
|
49
|
+
if @field.scope.present?
|
50
|
+
query = Avo::Hosts::AssociationScopeHost.new(block: @field.scope, query: query, parent: @model).handle
|
51
51
|
end
|
52
52
|
|
53
53
|
@options = query.all.map do |model|
|
@@ -65,7 +65,7 @@ module Avo
|
|
65
65
|
|
66
66
|
respond_to do |format|
|
67
67
|
if @model.save
|
68
|
-
format.html {
|
68
|
+
format.html { redirect_to resource_path(model: @model, resource: @resource), notice: t("avo.attachment_class_attached", attachment_class: @related_resource.name) }
|
69
69
|
else
|
70
70
|
format.html { render :new }
|
71
71
|
end
|
@@ -11,8 +11,8 @@ 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
|
-
|
15
|
-
|
14
|
+
before_action :reset_pagination_if_filters_changed, only: :index
|
15
|
+
before_action :cache_applied_filters, only: :index
|
16
16
|
|
17
17
|
def index
|
18
18
|
@page_title = @resource.plural_name.humanize
|
@@ -49,10 +49,10 @@ module Avo
|
|
49
49
|
# Check if the sortable field option is actually a proc and we need to do a custom sort
|
50
50
|
field_id = @index_params[:sort_by].to_sym
|
51
51
|
field = @resource.get_field_definitions.find { |field| field.id == field_id }
|
52
|
-
|
53
|
-
field.sortable.call(@query, @index_params[:sort_direction])
|
52
|
+
if field&.sortable.is_a?(Proc)
|
53
|
+
@query = field.sortable.call(@query, @index_params[:sort_direction])
|
54
54
|
else
|
55
|
-
@query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
|
55
|
+
@query = @query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
@@ -61,14 +61,7 @@ module Avo
|
|
61
61
|
@query = filter_class.safe_constantize.new.apply_query request, @query, filter_value
|
62
62
|
end
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
# Reset open filters when a user navigates to a new page
|
67
|
-
extra_pagy_params[:keep_filters_panel_open] = if params[:keep_filters_panel_open] == "1"
|
68
|
-
"0"
|
69
|
-
end
|
70
|
-
|
71
|
-
@pagy, @models = pagy(@query, items: @index_params[:per_page], link_extra: "data-turbo-frame=\"#{params[:turbo_frame]}\"", size: [1, 2, 2, 1], params: extra_pagy_params)
|
64
|
+
@pagy, @models = pagy(@query, items: @index_params[:per_page], link_extra: "data-turbo-frame=\"#{params[:turbo_frame]}\"", size: [1, 2, 2, 1])
|
72
65
|
|
73
66
|
# Create resources for each model
|
74
67
|
@resources = @models.map do |model|
|
@@ -361,21 +354,19 @@ module Avo
|
|
361
354
|
end
|
362
355
|
|
363
356
|
# Caching these so we know when the filters have changed so we reset the pagination
|
364
|
-
|
365
|
-
|
366
|
-
# session[:avo_applied_filters] = params[:filters]
|
367
|
-
# # ::Avo::App.cache_store.delete(applied_filters_cache_key) if params[:filters].nil?
|
357
|
+
def cache_applied_filters
|
358
|
+
::Avo::App.cache_store.delete applied_filters_cache_key if params[:filters].nil?
|
368
359
|
|
369
|
-
|
370
|
-
|
360
|
+
::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in: 1.day)
|
361
|
+
end
|
371
362
|
|
372
|
-
|
373
|
-
|
374
|
-
|
363
|
+
def reset_pagination_if_filters_changed
|
364
|
+
params[:page] = 1 if params[:filters] != ::Avo::App.cache_store.read(applied_filters_cache_key)
|
365
|
+
end
|
375
366
|
|
376
|
-
|
377
|
-
|
378
|
-
|
367
|
+
def applied_filters_cache_key
|
368
|
+
"avo.base_controller.#{@resource.model_key}.applied_filters"
|
369
|
+
end
|
379
370
|
|
380
371
|
def set_edit_title_and_breadcrumbs
|
381
372
|
@resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
|
@@ -51,12 +51,12 @@ module Avo
|
|
51
51
|
# Fetch the field
|
52
52
|
field = belongs_to_field
|
53
53
|
|
54
|
-
if field.
|
54
|
+
if field.scope.present?
|
55
55
|
# Fetch the parent
|
56
56
|
parent = params[:via_reflection_class].safe_constantize.find params[:via_reflection_id]
|
57
57
|
|
58
58
|
# Add to the query
|
59
|
-
query = Avo::Hosts::AssociationScopeHost.new(block: belongs_to_field.
|
59
|
+
query = Avo::Hosts::AssociationScopeHost.new(block: belongs_to_field.scope, query: query, parent: parent).handle
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
@@ -118,7 +118,7 @@ module Avo
|
|
118
118
|
end
|
119
119
|
|
120
120
|
def input_classes(extra_classes = "", has_error: false)
|
121
|
-
classes = "appearance-none inline-flex bg-gray-
|
121
|
+
classes = "appearance-none inline-flex bg-gray-100 disabled:cursor-not-allowed text-gray-600 disabled:opacity-50 rounded py-2 px-3 leading-tight border focus:border-gray-600 focus-visible:ring-0 focus:text-gray-700"
|
122
122
|
|
123
123
|
classes += if has_error
|
124
124
|
" border-red-600"
|
@@ -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,26 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ['container']
|
5
|
+
|
6
|
+
connect() {
|
7
|
+
window.toastr[this.type](this.message)
|
8
|
+
}
|
9
|
+
|
10
|
+
get type() {
|
11
|
+
const typeMap = {
|
12
|
+
info: 'info',
|
13
|
+
warning: 'warning',
|
14
|
+
success: 'success',
|
15
|
+
error: 'error',
|
16
|
+
notice: 'info',
|
17
|
+
alert: 'error',
|
18
|
+
}
|
19
|
+
|
20
|
+
return typeMap[this.containerTarget.dataset.alertType]
|
21
|
+
}
|
22
|
+
|
23
|
+
get message() {
|
24
|
+
return this.containerTarget.innerHTML
|
25
|
+
}
|
26
|
+
}
|
@@ -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
|
+
}
|
@@ -110,7 +110,7 @@ export default class extends Controller {
|
|
110
110
|
|
111
111
|
inputField(id = 'key', index, key, value) {
|
112
112
|
return `<input
|
113
|
-
class="${this.options.inputClasses}
|
113
|
+
class="${this.options.inputClasses} !rounded-none border-gray-600 border-r border-l-0 border-b-0 border-t-0 focus:border-gray-300 w-1/2 focus:outline-none outline-none key-value-input-${id}"
|
114
114
|
data-action="input->key-value#${id}FieldUpdated"
|
115
115
|
placeholder="${this.options[`${id}_label`]}"
|
116
116
|
data-index="${index}"
|
@@ -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 blacklistItems() {
|
18
|
+
return this.getJsonAttribute(this.inputTarget, 'data-blacklist-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.blacklistItems,
|
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
|
+
}
|
@@ -84,12 +84,9 @@ export default class extends Controller {
|
|
84
84
|
|
85
85
|
if (this.keepFiltersPanelOpenValue) {
|
86
86
|
// eslint-disable-next-line camelcase
|
87
|
-
query.keep_filters_panel_open = this.keepFiltersPanelOpenValue
|
87
|
+
query.keep_filters_panel_open = this.keepFiltersPanelOpenValue
|
88
88
|
}
|
89
89
|
|
90
|
-
// force to go to the first page if the filters changed
|
91
|
-
query.page = 1
|
92
|
-
|
93
90
|
if (encodedFilters) {
|
94
91
|
query.filters = encodedFilters
|
95
92
|
} else {
|
@@ -2,6 +2,7 @@ import { application } from './application'
|
|
2
2
|
|
3
3
|
import ActionController from './controllers/action_controller'
|
4
4
|
import ActionsPickerController from './controllers/actions_picker_controller'
|
5
|
+
import AlertsController from './controllers/alerts_controller'
|
5
6
|
import AttachmentsController from './controllers/attachments_controller'
|
6
7
|
import BelongsToFieldController from './controllers/fields/belongs_to_field_controller'
|
7
8
|
import BooleanFilterController from './controllers/boolean_filter_controller'
|
@@ -24,6 +25,7 @@ import SearchController from './controllers/search_controller'
|
|
24
25
|
import SelectController from './controllers/select_controller'
|
25
26
|
import SelectFilterController from './controllers/select_filter_controller'
|
26
27
|
import SimpleMdeController from './controllers/fields/simple_mde_controller'
|
28
|
+
import TagsFieldController from './controllers/fields/tags_field_controller'
|
27
29
|
import TextFilterController from './controllers/text_filter_controller'
|
28
30
|
import TippyController from './controllers/tippy_controller'
|
29
31
|
import TogglePanelController from './controllers/toggle_panel_controller'
|
@@ -31,6 +33,7 @@ import TrixFieldController from './controllers/fields/trix_field_controller'
|
|
31
33
|
|
32
34
|
application.register('action', ActionController)
|
33
35
|
application.register('actions-picker', ActionsPickerController)
|
36
|
+
application.register('alerts', AlertsController)
|
34
37
|
application.register('attachments', AttachmentsController)
|
35
38
|
application.register('boolean-filter', BooleanFilterController)
|
36
39
|
application.register('copy-to-clipboard', CopyToClipboardController)
|
@@ -48,6 +51,7 @@ application.register('per-page', PerPageController)
|
|
48
51
|
application.register('search', SearchController)
|
49
52
|
application.register('select', SelectController)
|
50
53
|
application.register('select-filter', SelectFilterController)
|
54
|
+
application.register('tags-field', TagsFieldController)
|
51
55
|
application.register('text-filter', TextFilterController)
|
52
56
|
application.register('tippy', TippyController)
|
53
57
|
application.register('toggle-panel', TogglePanelController)
|
@@ -29,14 +29,11 @@
|
|
29
29
|
</div>
|
30
30
|
<% end %>
|
31
31
|
<% c.controls do %>
|
32
|
-
<%= a_button data: { action: 'click->modal#close' },
|
33
|
-
size: :sm,
|
34
|
-
color: :primary do %>
|
32
|
+
<%= a_button data: { action: 'click->modal#close' }, size: :sm do %>
|
35
33
|
<%= @action.cancel_button_label %>
|
36
34
|
<% end %>
|
37
35
|
<%= a_button type: :submit,
|
38
|
-
color: :
|
39
|
-
style: :primary,
|
36
|
+
color: :green,
|
40
37
|
size: :sm,
|
41
38
|
data: @action.class.submit_button_data_attributes do %>
|
42
39
|
<%= @action.confirm_button_label %>
|
@@ -1,20 +1,14 @@
|
|
1
1
|
<div
|
2
|
-
class="
|
2
|
+
class="relative bg-white p-2 w-full flex flex-shrink-0 items-center z-50 px-4 lg:px-8 border-b space-x-4 lg:space-x-0 min-h-[4rem] <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>"
|
3
3
|
v-if="layout !== 'blank'"
|
4
4
|
>
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
<%= render partial: "avo/partials/logo" %>
|
9
|
-
</div>
|
10
|
-
|
11
|
-
|
12
|
-
<div class="flex-1 flex items-center justify-between lg:justify-start space-x-8 pl-4">
|
13
|
-
<div class="flex">
|
14
|
-
<%= render partial: "avo/partials/global_search" if ::Avo::App.license.has_with_trial(:global_search) && ::Avo.configuration.feature_enabled?(:global_search) %>
|
15
|
-
</div>
|
5
|
+
<%= a_button class: 'lg:hidden', icon: 'menu', data: { action: 'click->mobile#toggleSidebar' } %>
|
6
|
+
<div class="flex-1 flex items-center justify-between lg:justify-start space-x-8">
|
16
7
|
<div class="m-0">
|
17
8
|
<%= render partial: "avo/partials/header" %>
|
18
9
|
</div>
|
10
|
+
<div class="flex">
|
11
|
+
<%= render partial: "avo/partials/global_search" if ::Avo::App.license.has_with_trial(:global_search) && ::Avo.configuration.feature_enabled?(:global_search) %>
|
12
|
+
</div>
|
19
13
|
</div>
|
20
14
|
</div>
|