headmin 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +6 -0
- data/Gemfile.lock +92 -90
- data/README.md +2 -2
- data/app/assets/javascripts/headmin/controllers/filter_controller.js +15 -3
- data/app/assets/javascripts/headmin/controllers/filter_row_controller.js +75 -47
- data/app/assets/javascripts/headmin/controllers/infinite_scroller_controller.js +30 -0
- data/app/assets/javascripts/headmin/controllers/media_controller.js +24 -8
- data/app/assets/javascripts/headmin/controllers/media_modal_controller.js +142 -105
- data/app/assets/javascripts/headmin/index.js +2 -0
- data/app/assets/javascripts/headmin.js +122 -19
- data/app/assets/stylesheets/headmin.css +1 -1
- data/app/controllers/headmin/media_controller.rb +11 -0
- data/app/helpers/headmin/form_helper.rb +0 -11
- data/app/models/concerns/headmin/attachment.rb +34 -0
- data/app/models/headmin/filter/association.rb +0 -12
- data/app/models/headmin/filter/association_count.rb +50 -0
- data/app/models/headmin/filter/association_count_view.rb +78 -0
- data/app/models/headmin/filter/base.rb +10 -0
- data/app/models/headmin/filter/date.rb +8 -1
- data/app/models/headmin/filter/date_view.rb +1 -1
- data/app/models/headmin/filter/number.rb +0 -12
- data/app/models/headmin/filter/operator_view.rb +3 -1
- data/app/models/headmin/form/media_view.rb +6 -2
- data/app/views/headmin/_filters.html.erb +3 -8
- data/app/views/headmin/_form.html.erb +1 -1
- data/app/views/headmin/_index.html.erb +1 -1
- data/app/views/headmin/filters/_association_count.html.erb +28 -0
- data/app/views/headmin/filters/_date.html.erb +1 -0
- data/app/views/headmin/forms/fields/_base.html.erb +1 -1
- data/app/views/headmin/layout/_content.html.erb +1 -1
- data/app/views/headmin/media/_media_item_modal.html.erb +26 -0
- data/app/views/headmin/media/_modal.html.erb +8 -3
- data/app/views/headmin/media/_thumbnail.html.erb +20 -0
- data/app/views/headmin/media/create.turbo_stream.erb +1 -1
- data/app/views/headmin/media/index.turbo_stream.erb +11 -0
- data/app/views/headmin/media/thumbnail.html.erb +3 -0
- data/app/views/headmin/nav/_item.html.erb +6 -1
- data/app/views/headmin/pagination/_infinite.html.erb +7 -0
- data/config/initializers/extend_active_storage_attachment.rb +3 -0
- data/config/locales/headmin/filters/en.yml +2 -0
- data/config/locales/headmin/filters/nl.yml +2 -0
- data/config/locales/headmin/media/en.yml +1 -0
- data/config/locales/headmin/media/nl.yml +1 -0
- data/config/locales/headmin/pagination/en.yml +2 -0
- data/config/locales/headmin/pagination/nl.yml +2 -0
- data/config/routes.rb +2 -1
- data/lib/headmin/version.rb +1 -1
- data/package.json +1 -1
- metadata +12 -3
- data/app/views/headmin/media/_item.html.erb +0 -16
@@ -0,0 +1,50 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filter
|
3
|
+
class AssociationCount < Headmin::Filter::Base
|
4
|
+
OPERATORS = %w[eq not_eq gt gteq lt lteq]
|
5
|
+
|
6
|
+
def cast_value(value)
|
7
|
+
is_i?(value) ? value.to_i : 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def query(collection)
|
11
|
+
return collection unless @instructions.any?
|
12
|
+
|
13
|
+
# Store the collections' class for later use
|
14
|
+
@parent_class = collection.is_a?(Class) ? collection : collection.klass
|
15
|
+
|
16
|
+
# Join table and group on primary key if necessary
|
17
|
+
collection = collection.left_joins(reflection.name).group(primary_key)
|
18
|
+
|
19
|
+
# Build query and execute
|
20
|
+
query = nil
|
21
|
+
@instructions.each do |instruction|
|
22
|
+
query = build_query(query, collection, instruction)
|
23
|
+
end
|
24
|
+
collection.having(query)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def build_query(query, collection, instruction)
|
30
|
+
query_operator = convert_to_query_operator(instruction[:operator])
|
31
|
+
query_value = convert_to_query_value(instruction[:value], instruction[:operator])
|
32
|
+
|
33
|
+
query_operator, query_value = process_null_operators(query_operator, query_value)
|
34
|
+
new_query = reflection.klass.arel_table[reflection.foreign_key.to_sym].count.send(query_operator, query_value)
|
35
|
+
query ? query.send(instruction[:conditional], new_query) : new_query
|
36
|
+
end
|
37
|
+
|
38
|
+
def reflection
|
39
|
+
reflection = @parent_class.reflect_on_association(attribute.to_s.split("_")[0].to_sym)
|
40
|
+
raise UnknownAssociation if reflection.nil?
|
41
|
+
|
42
|
+
reflection
|
43
|
+
end
|
44
|
+
|
45
|
+
def primary_key
|
46
|
+
"#{@parent_class.table_name}.#{@parent_class.primary_key}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filter
|
3
|
+
class AssociationCountView < FilterView
|
4
|
+
def base_options
|
5
|
+
keys = %i[name label form]
|
6
|
+
options = to_h.slice(*keys)
|
7
|
+
default_base_options.merge(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def input_options
|
11
|
+
keys = %i[form]
|
12
|
+
options = to_h.slice(*keys)
|
13
|
+
default_input_options.merge(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def collection
|
17
|
+
@collection || association_model.all.map { |record| [record.to_s, record.id] }
|
18
|
+
end
|
19
|
+
|
20
|
+
def association_model
|
21
|
+
reflection.klass
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def id
|
27
|
+
"#{name}_value"
|
28
|
+
end
|
29
|
+
|
30
|
+
def name
|
31
|
+
@name || attribute
|
32
|
+
end
|
33
|
+
|
34
|
+
def attribute
|
35
|
+
"#{@association}_count"
|
36
|
+
end
|
37
|
+
|
38
|
+
def label
|
39
|
+
@label || I18n.t("attributes.#{attribute}", default: "#{I18n.t("attributes.count")} #{association_model.model_name.human(count: collection? ? 2 : 1)}")
|
40
|
+
end
|
41
|
+
|
42
|
+
def reflection
|
43
|
+
form.object.class.reflect_on_association(@association)
|
44
|
+
end
|
45
|
+
|
46
|
+
def collection?
|
47
|
+
reflection.collection?
|
48
|
+
end
|
49
|
+
|
50
|
+
def default_base_options
|
51
|
+
{
|
52
|
+
label: label,
|
53
|
+
name: attribute,
|
54
|
+
display_values: collection,
|
55
|
+
filter: Headmin::Filter::AssociationCount.new(name, @params),
|
56
|
+
allowed_operators: Headmin::Filter::AssociationCount::OPERATORS
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def default_input_options
|
61
|
+
{
|
62
|
+
label: false,
|
63
|
+
wrapper: false,
|
64
|
+
name: nil,
|
65
|
+
id: id,
|
66
|
+
data: {
|
67
|
+
action: "change->filter#updateHiddenValue",
|
68
|
+
filter_target: "value",
|
69
|
+
filter_row_target: "original"
|
70
|
+
},
|
71
|
+
collection: collection,
|
72
|
+
selected: selected,
|
73
|
+
class: "form-control"
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -100,6 +100,16 @@ module Headmin
|
|
100
100
|
query ? query.send(instruction[:conditional], new_query) : new_query
|
101
101
|
end
|
102
102
|
|
103
|
+
def is_i?(value)
|
104
|
+
# Regex: this selects signed digits (\d) only, it is then checked to the value, e.g.:
|
105
|
+
# is_i?("3") = true
|
106
|
+
# is_i?("-3") = true
|
107
|
+
# is_i?("3a") = false
|
108
|
+
# is_i?("3.2") = false
|
109
|
+
|
110
|
+
/\A[-+]?\d+\z/.match(value)
|
111
|
+
end
|
112
|
+
|
103
113
|
private
|
104
114
|
|
105
115
|
def parse(string)
|
@@ -56,8 +56,15 @@ module Headmin
|
|
56
56
|
def display_value(value)
|
57
57
|
# This uses the default date format of headmin.
|
58
58
|
# Can be overwritten by setting default date format of the application.
|
59
|
-
|
59
|
+
|
60
|
+
current_operator = instructions.find { |instruction| instruction[:value] == value }[:operator]
|
61
|
+
|
62
|
+
# To make the operators eq and not_eq work, we pass a range.
|
63
|
+
# However, display value should return this as a date and not a range.
|
64
|
+
if value.class.to_s == "Range" && (current_operator == "eq" || current_operator == "not_eq")
|
60
65
|
I18n.l(value.last.to_date)
|
66
|
+
elsif values.class.to_s
|
67
|
+
"#{I18n.l(value.first.to_date)} - #{I18n.l(value.last.to_date)}"
|
61
68
|
else
|
62
69
|
I18n.l(value.to_date)
|
63
70
|
end
|
@@ -28,7 +28,7 @@ module Headmin
|
|
28
28
|
label: label,
|
29
29
|
name: attribute,
|
30
30
|
filter: Headmin::Filter::Date.new(name, @params),
|
31
|
-
allowed_operators: Headmin::Filter::Date::OPERATORS - %w[in not_in
|
31
|
+
allowed_operators: Headmin::Filter::Date::OPERATORS - %w[in not_in]
|
32
32
|
}
|
33
33
|
end
|
34
34
|
|
@@ -10,18 +10,6 @@ module Headmin
|
|
10
10
|
def to_s
|
11
11
|
string
|
12
12
|
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def is_i?(value)
|
17
|
-
# Regex: this selects signed digits (\d) only, it is then checked to the value, e.g.:
|
18
|
-
# is_i?("3") = true
|
19
|
-
# is_i?("-3") = true
|
20
|
-
# is_i?("3a") = false
|
21
|
-
# is_i?("3.2") = false
|
22
|
-
|
23
|
-
/\A[-+]?\d+\z/.match(value)
|
24
|
-
end
|
25
13
|
end
|
26
14
|
end
|
27
15
|
end
|
@@ -24,7 +24,9 @@ module Headmin
|
|
24
24
|
is_null: "○ #{I18n.t("headmin.filters.operators.is_null")}",
|
25
25
|
is_not_null: "● #{I18n.t("headmin.filters.operators.is_not_null")}",
|
26
26
|
in: "∋ #{I18n.t("headmin.filters.operators.in")}",
|
27
|
-
not_in: "∌ #{I18n.t("headmin.filters.operators.not_in")}"
|
27
|
+
not_in: "∌ #{I18n.t("headmin.filters.operators.not_in")}",
|
28
|
+
between: "↔ #{I18n.t("headmin.filters.operators.between")}",
|
29
|
+
not_between: "⥈ #{I18n.t("headmin.filters.operators.not_between")}"
|
28
30
|
}
|
29
31
|
end
|
30
32
|
end
|
@@ -66,7 +66,9 @@ module Headmin
|
|
66
66
|
def association_object
|
67
67
|
if attached.is_a?(ActiveStorage::Attached::Many)
|
68
68
|
result = form.object.send(nested_attribute)
|
69
|
-
|
69
|
+
|
70
|
+
# We sort the collection with ruby to prevent a new query to be made that would dispose of the objects in memory
|
71
|
+
result = result.sort_by { |item| item.position } if sort
|
70
72
|
result
|
71
73
|
else
|
72
74
|
form.object.send(nested_attribute)
|
@@ -76,7 +78,9 @@ module Headmin
|
|
76
78
|
def attachments
|
77
79
|
if attached.is_a?(ActiveStorage::Attached::Many)
|
78
80
|
result = form.object.send(nested_attribute)
|
79
|
-
|
81
|
+
|
82
|
+
# We sort the collection with ruby to prevent a new query to be made that would dispose of the objects in memory
|
83
|
+
result = result.sort_by { |item| item.position } if sort
|
80
84
|
result.to_a.compact
|
81
85
|
else
|
82
86
|
[form.object.send(nested_attribute)].compact
|
@@ -14,21 +14,16 @@
|
|
14
14
|
# <%= render "headmin/filters", url: admin_polls_path %#>
|
15
15
|
|
16
16
|
action = local_assigns.has_key?(:url) ? url : request.path
|
17
|
-
|
18
|
-
begin
|
19
|
-
model = controller_name.classify.constantize
|
20
|
-
rescue
|
21
|
-
raise "Cannot find class!"
|
22
|
-
end
|
17
|
+
model = controller_name.classify.constantize rescue nil
|
23
18
|
%>
|
24
19
|
|
25
|
-
<%= form_with model: model
|
20
|
+
<%= form_with model: model&.new, url: action, method: :get, data: {controller: "filters", filters_target: "form"} do |form| %>
|
26
21
|
<%# Perform yield in order to capture content blocks, pass form so we can use headmin form inputs %>
|
27
22
|
<%= yield(form) %>
|
28
23
|
|
29
24
|
<!-- Default parameters (e.g. sorting, pagination) -->
|
30
25
|
<% default_params.except("page").each do |name, value| %>
|
31
|
-
<%=
|
26
|
+
<%= hidden_field_tag(name, value) %>
|
32
27
|
<% end %>
|
33
28
|
|
34
29
|
<div class="d-flex flex-column flex-md-row align-content-start align-items-md-start">
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<%
|
2
|
+
# headmin/filters/association_count
|
3
|
+
#
|
4
|
+
# ==== Required parameters
|
5
|
+
# * +association+ - Name of the association that has to be counted
|
6
|
+
# * +form+ - Form object
|
7
|
+
#
|
8
|
+
# ==== Optional parameters
|
9
|
+
# * +label+ - Display label
|
10
|
+
# * +name+ - Name of the filter parameter
|
11
|
+
#
|
12
|
+
# ==== Examples
|
13
|
+
# Basic version (one-to-many)
|
14
|
+
# <%= render "headmin/filters", url: admin_orders_path do |form| %#>
|
15
|
+
# <%= render "headmin/filters/association_count", form: form, association: :beverages %#>
|
16
|
+
# <% end %#>
|
17
|
+
#
|
18
|
+
# Basic version (one-to-one)
|
19
|
+
# <%= render "headmin/filters", url: admin_orders_path do |form| %#>
|
20
|
+
# <%= render "headmin/filters/association_count", form: form, association: :beverage %#>
|
21
|
+
# <% end %#>
|
22
|
+
|
23
|
+
number = Headmin::Filter::AssociationCountView.new(local_assigns.merge(params: params))
|
24
|
+
%>
|
25
|
+
|
26
|
+
<%= render "headmin/filters/base", number.base_options do |value| %>
|
27
|
+
<%= render "headmin/forms/number", number.input_options.merge({value: value}) %>
|
28
|
+
<% end %>
|
@@ -20,4 +20,5 @@
|
|
20
20
|
|
21
21
|
<%= render "headmin/filters/base", date.base_options do |value| %>
|
22
22
|
<%= render "headmin/forms/date", date.input_options.merge({value: value}) %>
|
23
|
+
<%= render "headmin/forms/date_range", date.input_options.merge({start: {value: value.class.to_s == "Range" ? value.first : value}, end: {value: value.class.to_s == "Range" ? value.last : value}}) %>
|
23
24
|
<% end %>
|
@@ -11,7 +11,6 @@
|
|
11
11
|
# <%= render "headmin/forms/fields/base", form: form, field_type: :text, name: name do |field, label| %#>
|
12
12
|
# <%= render "headmin/forms/text", form: field, attribute: :value, label: label %#>
|
13
13
|
# <% end %#>
|
14
|
-
|
15
14
|
field = form.object.fields.detect { |field| field.name.to_s == name.to_s }
|
16
15
|
field = field ? field : form.object.fields.new(field_type: field_type, name: name)
|
17
16
|
label = t("attributes.#{name.to_s.parameterize}", default: field.name.to_s)
|
@@ -21,5 +20,6 @@
|
|
21
20
|
<%= ff.hidden_field :id %>
|
22
21
|
<%= ff.hidden_field :name %>
|
23
22
|
<%= ff.hidden_field :field_type %>
|
23
|
+
|
24
24
|
<%= yield ff, label %>
|
25
25
|
<% end %>
|
@@ -42,6 +42,32 @@
|
|
42
42
|
<%= render "headmin/forms/text", form: form, attribute: :filename, append: "." + form.object.filename.to_s.rpartition(".").last, value: form.object.filename.to_s.rpartition(".").first %>
|
43
43
|
</div>
|
44
44
|
</div>
|
45
|
+
<% attachments = @blob.attachments.not_a_variant.all %>
|
46
|
+
<% if attachments.any? %>
|
47
|
+
<hr>
|
48
|
+
<h6><%= t(".attachment_used_by") %>:</h6>
|
49
|
+
<table class="table table-sm">
|
50
|
+
<tbody>
|
51
|
+
<% attachments.each do |attachment| %>
|
52
|
+
<% context_resource = attachment.record_hierarchy.last %>
|
53
|
+
<% context_resource_path = polymorphic_path([:admin, context_resource]) rescue nil %>
|
54
|
+
<tr>
|
55
|
+
<td width="40%">
|
56
|
+
<% if context_resource_path.present? %>
|
57
|
+
<%= link_to truncate(context_resource.to_s), context_resource_path, title: context_resource %>
|
58
|
+
<% else %>
|
59
|
+
<%= context_resource %>
|
60
|
+
<% end %>
|
61
|
+
</td>
|
62
|
+
<td width="25%"><%= "#{context_resource.model_name.human(count: 1)} (#{attachment.class.human_attribute_name(attachment.name.to_sym)})" %></td>
|
63
|
+
<td width="35%">
|
64
|
+
<small class="text-secondary"><%= l(attachment.created_at, format: :long) %></small>
|
65
|
+
</td>
|
66
|
+
</tr>
|
67
|
+
<% end %>
|
68
|
+
</tbody>
|
69
|
+
</table>
|
70
|
+
<% end %>
|
45
71
|
</div>
|
46
72
|
<div class="modal-footer">
|
47
73
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= t(".close") %></button>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<div class="media-modal modal fade" tabindex="-1" data-controller="remote-modal media-modal" data-name="<%= name %>" data-min="<%= min %>" data-max="<%= max %>">
|
1
|
+
<div class="media-modal modal fade" tabindex="-1" data-controller="remote-modal media-modal" data-media-modal-ids-value="<%= params[:ids] ? params[:ids] : [] %>" data-name="<%= name %>" data-min="<%= min %>" data-max="<%= max %>">
|
2
2
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
3
3
|
<div class="modal-content">
|
4
4
|
<div class="modal-header">
|
@@ -10,15 +10,20 @@
|
|
10
10
|
<div class="modal-body">
|
11
11
|
<%= turbo_frame_tag "thumbnails", class: "d-flex flex-wrap gap-2" do %>
|
12
12
|
<% @blobs.each do |blob| %>
|
13
|
-
<%=
|
13
|
+
<%= turbo_frame_tag blob, src: headmin_media_item_thumbnail_path(blob), loading: "lazy" do %>
|
14
|
+
<%= render "thumbnail" %>
|
15
|
+
<% end %>
|
14
16
|
<% end %>
|
15
17
|
<div data-media-modal-target="placeholder" class="<%= "d-none" if !@blobs.empty? %>">
|
16
18
|
<p><%= t(".placeholder") %></p>
|
17
19
|
</div>
|
18
20
|
<% end %>
|
21
|
+
<div class="mt-3">
|
22
|
+
<%= render "headmin/pagination/infinite", items: @blobs %>
|
23
|
+
</div>
|
19
24
|
</div>
|
20
25
|
<div class="modal-footer">
|
21
|
-
<%= form_with url:
|
26
|
+
<%= form_with url: headmin_new_media_path, multipart: true, data: {"media-modal-target": "form"}, class: "me-auto" do |form| %>
|
22
27
|
<%= form.label :files, class: "btn h-btn-outline-light" do %>
|
23
28
|
<%= bootstrap_icon("upload") %>
|
24
29
|
<%= t(".upload") %>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<% blob = local_assigns.has_key?(:blob) ? local_assigns[:blob] : nil %>
|
2
|
+
<% if blob.present? %>
|
3
|
+
<div data-media-modal-target="item" title="<%= "#{blob.filename} (#{l(blob.created_at, format: :long)})" %>">
|
4
|
+
<!-- Input -->
|
5
|
+
<input
|
6
|
+
id="media-item-<%= blob.id %>"
|
7
|
+
type="checkbox"
|
8
|
+
value="<%= blob.id %>"
|
9
|
+
data-action="change->media-modal#inputChange"
|
10
|
+
data-media-modal-target="idCheckbox"
|
11
|
+
hidden>
|
12
|
+
|
13
|
+
<!-- Label -->
|
14
|
+
<label for="media-item-<%= blob.id %>">
|
15
|
+
<%= render "headmin/thumbnail", file: blob %>
|
16
|
+
</label>
|
17
|
+
</div>
|
18
|
+
<% else %>
|
19
|
+
<%= render "headmin/thumbnail", file: nil %>
|
20
|
+
<% end %>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%= turbo_stream.append "thumbnails" do %>
|
2
|
+
<% @blobs.each do |blob| %>
|
3
|
+
<%= turbo_frame_tag blob, src: headmin_media_item_thumbnail_path(blob), loading: "lazy" do %>
|
4
|
+
<%= render 'thumbnail' %>
|
5
|
+
<% end %>
|
6
|
+
<% end %>
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
<%= turbo_stream.replace "infinite" do %>
|
10
|
+
<%= render "headmin/pagination/infinite", items: @blobs %>
|
11
|
+
<% end %>
|
@@ -5,6 +5,7 @@
|
|
5
5
|
# * +name</tt> - Name of the nav item
|
6
6
|
# * +url</tt> - URL for this nav item
|
7
7
|
# * +icon</tt> - Optional Bootstrap icon name
|
8
|
+
# * +locked</tt> - Item becomes unclickable and a lock icon appears at the end of the item
|
8
9
|
# * +active</tt> - Set to true if this nav item needs to be highlighted
|
9
10
|
#
|
10
11
|
# ==== Examples
|
@@ -17,14 +18,18 @@
|
|
17
18
|
name = local_assigns.has_key?(:name) ? name : ""
|
18
19
|
icon = local_assigns.has_key?(:icon) ? icon : nil
|
19
20
|
url = local_assigns.has_key?(:url) ? url : request.url
|
21
|
+
locked = local_assigns.has_key?(:locked) ? locked : false
|
20
22
|
active = local_assigns.has_key?(:active) ? active : current_url?(url)
|
21
23
|
%>
|
22
24
|
|
23
25
|
<li class="nav-item">
|
24
|
-
<a class="nav-link d-flex align-items-center <%= "active" if active %>" aria-current="page" href="<%= url %>">
|
26
|
+
<a class="nav-link d-flex align-items-center <%= "active" if active %> <%= "disabled" if locked %>" aria-current="page" href="<%= url %>">
|
25
27
|
<%= bootstrap_icon(icon, class: "me-2") if icon %>
|
26
28
|
<span class="d-block d-md-none d-lg-block">
|
27
29
|
<%= name %>
|
28
30
|
</span>
|
31
|
+
<% if locked %>
|
32
|
+
<%= bootstrap_icon("lock", class: "fs-6 ms-2") %>
|
33
|
+
<% end %>
|
29
34
|
</a>
|
30
35
|
</li>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<% auto_load = local_assigns.has_key?(:auto_load) ? local_assigns[:auto_load] : true %>
|
2
|
+
|
3
|
+
<% unless items.page(params[:page]).last_page? %>
|
4
|
+
<div id="infinite">
|
5
|
+
<%= link_to t(".load_more"), path_to_next_page(items), class: "btn btn-primary", data: {turbo_stream: true, controller: auto_load ? "infinite-scroller" : ""} %>
|
6
|
+
</div>
|
7
|
+
<% end %>
|
data/config/routes.rb
CHANGED
@@ -3,8 +3,9 @@
|
|
3
3
|
Rails.application.routes.draw do
|
4
4
|
namespace(:headmin) do
|
5
5
|
get "media", to: "media#index", as: :media
|
6
|
-
post "media", to: "media#create"
|
6
|
+
post "media", to: "media#create", as: :new_media
|
7
7
|
get "media/:id", to: "media#show", as: :media_item
|
8
8
|
patch "media/:id", to: "media#update"
|
9
|
+
get "media/thumbnail/:id", to: "media#thumbnail", as: :media_item_thumbnail
|
9
10
|
end
|
10
11
|
end
|
data/lib/headmin/version.rb
CHANGED
data/package.json
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: headmin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jef Vlamings
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-02-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: closure_tree
|
@@ -105,6 +105,7 @@ files:
|
|
105
105
|
- app/assets/javascripts/headmin/controllers/filters_controller.js
|
106
106
|
- app/assets/javascripts/headmin/controllers/flatpickr_controller.js
|
107
107
|
- app/assets/javascripts/headmin/controllers/hello_controller.js
|
108
|
+
- app/assets/javascripts/headmin/controllers/infinite_scroller_controller.js
|
108
109
|
- app/assets/javascripts/headmin/controllers/media_controller.js
|
109
110
|
- app/assets/javascripts/headmin/controllers/media_modal_controller.js
|
110
111
|
- app/assets/javascripts/headmin/controllers/notification_controller.js
|
@@ -244,6 +245,7 @@ files:
|
|
244
245
|
- app/helpers/headmin/form_helper.rb
|
245
246
|
- app/helpers/headmin/notification_helper.rb
|
246
247
|
- app/helpers/headmin/request_helper.rb
|
248
|
+
- app/models/concerns/headmin/attachment.rb
|
247
249
|
- app/models/concerns/headmin/blob.rb
|
248
250
|
- app/models/concerns/headmin/block.rb
|
249
251
|
- app/models/concerns/headmin/blockable.rb
|
@@ -260,6 +262,8 @@ files:
|
|
260
262
|
- app/models/headmin/.DS_Store
|
261
263
|
- app/models/headmin/blocks_view.rb
|
262
264
|
- app/models/headmin/filter/association.rb
|
265
|
+
- app/models/headmin/filter/association_count.rb
|
266
|
+
- app/models/headmin/filter/association_count_view.rb
|
263
267
|
- app/models/headmin/filter/association_view.rb
|
264
268
|
- app/models/headmin/filter/base.rb
|
265
269
|
- app/models/headmin/filter/base_view.rb
|
@@ -337,6 +341,7 @@ files:
|
|
337
341
|
- app/views/headmin/dropdown/_list.html.erb
|
338
342
|
- app/views/headmin/dropdown/_locale.html.erb
|
339
343
|
- app/views/headmin/filters/_association.html.erb
|
344
|
+
- app/views/headmin/filters/_association_count.html.erb
|
340
345
|
- app/views/headmin/filters/_base.html.erb
|
341
346
|
- app/views/headmin/filters/_boolean.html.erb
|
342
347
|
- app/views/headmin/filters/_date.html.erb
|
@@ -407,17 +412,20 @@ files:
|
|
407
412
|
- app/views/headmin/layout/_sidebar.html.erb
|
408
413
|
- app/views/headmin/layout/sidebar/_bottom.html.erb
|
409
414
|
- app/views/headmin/layout/sidebar/_nav.html.erb
|
410
|
-
- app/views/headmin/media/_item.html.erb
|
411
415
|
- app/views/headmin/media/_media_item_modal.html.erb
|
412
416
|
- app/views/headmin/media/_modal.html.erb
|
417
|
+
- app/views/headmin/media/_thumbnail.html.erb
|
413
418
|
- app/views/headmin/media/create.turbo_stream.erb
|
414
419
|
- app/views/headmin/media/index.html.erb
|
420
|
+
- app/views/headmin/media/index.turbo_stream.erb
|
415
421
|
- app/views/headmin/media/show.html.erb
|
422
|
+
- app/views/headmin/media/thumbnail.html.erb
|
416
423
|
- app/views/headmin/media/update.turbo_stream.erb
|
417
424
|
- app/views/headmin/nav/_dropdown.html.erb
|
418
425
|
- app/views/headmin/nav/_item.html.erb
|
419
426
|
- app/views/headmin/nav/item/_devise.html.erb
|
420
427
|
- app/views/headmin/nav/item/_locale.html.erb
|
428
|
+
- app/views/headmin/pagination/_infinite.html.erb
|
421
429
|
- app/views/headmin/pagination/_per_page.html.erb
|
422
430
|
- app/views/headmin/pagination/kaminari/_first_page.html.erb
|
423
431
|
- app/views/headmin/pagination/kaminari/_gap.html.erb
|
@@ -468,6 +476,7 @@ files:
|
|
468
476
|
- bin/setup
|
469
477
|
- config/importmap.rb
|
470
478
|
- config/initializers/customize_input_error.rb
|
479
|
+
- config/initializers/extend_active_storage_attachment.rb
|
471
480
|
- config/initializers/extend_active_storage_blob.rb
|
472
481
|
- config/locales/activerecord/en.yml
|
473
482
|
- config/locales/activerecord/nl.yml
|