summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/assets_controller.rb8
-rw-r--r--app/controllers/events_controller.rb7
-rw-r--r--app/models/asset.rb14
-rw-r--r--app/models/event.rb18
-rw-r--r--app/views/assets/index.html.erb2
-rw-r--r--app/views/events/index.html.erb2
-rw-r--r--app/views/nodes/_node_list.html.erb17
-rw-r--r--app/views/shared/_search_form.html.erb15
-rw-r--r--config/locales/de.yml2
-rw-r--r--config/locales/en.yml2
-rw-r--r--public/stylesheets/admin.css8
-rw-r--r--test/models/asset_test.rb27
-rw-r--r--test/models/event_test.rb25
13 files changed, 122 insertions, 25 deletions
diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb
index 801e0968..f8a7d1e4 100644
--- a/app/controllers/assets_controller.rb
+++ b/app/controllers/assets_controller.rb
@@ -6,12 +6,10 @@ class AssetsController < ApplicationController
6 before_action :login_required 6 before_action :login_required
7 7
8 layout 'admin' 8 layout 'admin'
9 9
10 def index 10 def index
11 @assets = Asset.order('id DESC').paginate( 11 scope = params[:q].present? ? Asset.editor_search(params[:q]) : Asset.all
12 :page => params[:page], 12 @assets = scope.order("id DESC").paginate(:page => params[:page], :per_page => 20)
13 :per_page => 20
14 )
15 end 13 end
16 14
17 # GET /assets/1 15 # GET /assets/1
diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb
index d581de16..7ace5c1d 100644
--- a/app/controllers/events_controller.rb
+++ b/app/controllers/events_controller.rb
@@ -10,11 +10,12 @@ class EventsController < ApplicationController
10 # GET /events 10 # GET /events
11 # GET /events.xml 11 # GET /events.xml
12 def index 12 def index
13 @events = Event.order(:id) 13 scope = Event.order(Arel.sql("start_time DESC NULLS LAST"))
14 scope = scope.merge(Event.editor_search(params[:q])) if params[:q].present?
14 15
15 respond_to do |format| 16 respond_to do |format|
16 format.html { @events = @events.paginate(page: params[:page], per_page: 25) } 17 format.html { @events = scope.paginate(:page => params[:page], :per_page => 25) }
17 format.xml { render :xml => @events } 18 format.xml { render :xml => scope }
18 end 19 end
19 end 20 end
20 21
diff --git a/app/models/asset.rb b/app/models/asset.rb
index b256b929..4d43b18f 100644
--- a/app/models/asset.rb
+++ b/app/models/asset.rb
@@ -82,4 +82,18 @@ class Asset < ApplicationRecord
82 destroy! 82 destroy!
83 end 83 end
84 end 84 end
85
86 def self.editor_search(term)
87 words = term.to_s.split(/\s+/).reject(&:blank?)
88 return none if words.empty?
89
90 words.inject(all) do |scope, word|
91 like = "%#{sanitize_sql_like(word)}%"
92 scope.where(
93 "assets.name ILIKE :t OR assets.creator ILIKE :t OR " \
94 "assets.upload_file_name ILIKE :t OR assets.source_url ILIKE :t OR " \
95 "assets.upload_content_type ILIKE :t", :t => like
96 )
97 end
98 end
85end 99end
diff --git a/app/models/event.rb b/app/models/event.rb
index 792ab448..e7823617 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -54,6 +54,24 @@ class Event < ApplicationRecord
54 destroyed 54 destroyed
55 end 55 end
56 56
57 def self.editor_search(term)
58 words = term.to_s.split(/\s+/).reject(&:blank?)
59 return none if words.empty?
60
61 words.inject(all) do |scope, word|
62 like = "%#{sanitize_sql_like(word)}%"
63 tagged = ActsAsTaggableOn::Tagging
64 .where(:taggable_type => base_class.name)
65 .joins(:tag).where("tags.name ILIKE ?", like)
66 .select(:taggable_id)
67 scope.where(
68 "events.title ILIKE :t OR events.description ILIKE :t OR " \
69 "events.location ILIKE :t OR events.url ILIKE :t OR " \
70 "events.id IN (:tagged)", :t => like, :tagged => tagged
71 )
72 end
73 end
74
57 private 75 private
58 def generate_occurrences 76 def generate_occurrences
59 Occurrence.generate self 77 Occurrence.generate self
diff --git a/app/views/assets/index.html.erb b/app/views/assets/index.html.erb
index af11f54f..d2c5d6cc 100644
--- a/app/views/assets/index.html.erb
+++ b/app/views/assets/index.html.erb
@@ -4,6 +4,8 @@
4 :label => t(".create_asset"), 4 :label => t(".create_asset"),
5 :options => [[t(".create_asset"), new_asset_path]] %> 5 :options => [[t(".create_asset"), new_asset_path]] %>
6 6
7<%= render "shared/search_form", :placeholder => t(".search_placeholder") %>
8
7<%= will_paginate @assets %> 9<%= will_paginate @assets %>
8 10
9<table class="assets_table"> 11<table class="assets_table">
diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb
index c4f0a7f9..14790526 100644
--- a/app/views/events/index.html.erb
+++ b/app/views/events/index.html.erb
@@ -4,6 +4,8 @@
4 :label => t(".create_event"), 4 :label => t(".create_event"),
5 :options => [[t(".create_event"), new_event_path]] %> 5 :options => [[t(".create_event"), new_event_path]] %>
6 6
7<%= render "shared/search_form", :placeholder => t(".search_placeholder") %>
8
7<%= will_paginate @events %> 9<%= will_paginate @events %>
8 10
9<table class="events_table"> 11<table class="events_table">
diff --git a/app/views/nodes/_node_list.html.erb b/app/views/nodes/_node_list.html.erb
index f3101058..55f396b7 100644
--- a/app/views/nodes/_node_list.html.erb
+++ b/app/views/nodes/_node_list.html.erb
@@ -1,16 +1,7 @@
1<%= form_tag url_for(controller: params[:controller], action: params[:action]), method: :get, class: "node_search_form" do %> 1<%= render "shared/search_form",
2 <% Array(params[:kinds]).each do |kind| %> 2 :placeholder => t(".search_placeholder"),
3 <%= hidden_field_tag "kinds[]", kind %> 3 :hidden => Array(params[:kinds]).map { |k| ["kinds[]", k] } +
4 <% end %> 4 (params[:tags].present? ? [["tags", params[:tags]]] : []) %>
5 <%= hidden_field_tag :tags, params[:tags] if params[:tags].present? %>
6 <%= text_field_tag :q, params[:q], placeholder: t(".search_placeholder") %>
7 <%= button_tag type: "submit", class: "action_button" do %>
8 <%= icon("search", library: "tabler", "aria-hidden": true) %> <%= t("admin.common.search") %>
9 <% end %>
10 <% if params[:q].present? || params[:kinds].present? || params[:tags].present? %>
11 <%= link_to t("admin.common.reset"), url_for(controller: params[:controller], action: params[:action]) %>
12 <% end %>
13<% end %>
14 5
15<%= will_paginate @nodes %> 6<%= will_paginate @nodes %>
16<table class="node_table"> 7<table class="node_table">
diff --git a/app/views/shared/_search_form.html.erb b/app/views/shared/_search_form.html.erb
new file mode 100644
index 00000000..bff6dfaa
--- /dev/null
+++ b/app/views/shared/_search_form.html.erb
@@ -0,0 +1,15 @@
1<%= form_tag url_for(:controller => params[:controller], :action => params[:action]),
2 :method => :get, :class => "search_form" do %>
3 <% Array(local_assigns[:hidden]).each do |field_name, value| %>
4 <%= hidden_field_tag field_name, value %>
5 <% end %>
6 <%= text_field_tag :q, params[:q], :placeholder => placeholder %>
7 <%= button_tag :type => "submit", :class => "action_button" do %>
8 <%= icon("search", library: "tabler", "aria-hidden": true) %>
9 <%= t("admin.common.search") %>
10 <% end %>
11 <% if params[:q].present? || Array(local_assigns[:hidden]).any? %>
12 <%= link_to t("admin.common.reset"),
13 url_for(:controller => params[:controller], :action => params[:action]) %>
14 <% end %>
15<% end %>
diff --git a/config/locales/de.yml b/config/locales/de.yml
index a4181b9b..df52da4c 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -581,6 +581,7 @@ de:
581 title: "Termine" 581 title: "Termine"
582 create_event: "Termin anlegen" 582 create_event: "Termin anlegen"
583 edit_link: "bearbeiten" 583 edit_link: "bearbeiten"
584 search_placeholder: "Titel, Beschreibung, Ort, Tags durchsuchen…"
584 show: 585 show:
585 title: "Termin" 586 title: "Termin"
586 title_for_node: "Termin für Node %{path}" 587 title_for_node: "Termin für Node %{path}"
@@ -691,6 +692,7 @@ de:
691 index: 692 index:
692 title: "Assets" 693 title: "Assets"
693 create_asset: "Asset anlegen" 694 create_asset: "Asset anlegen"
695 search_placeholder: "Name, Urheber, Dateiname, Typ durchsuchen…"
694 show: 696 show:
695 thumbnail: "Vorschaubild" 697 thumbnail: "Vorschaubild"
696 attached_to: "Angehängt an" 698 attached_to: "Angehängt an"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 4cf7f61f..f67016b9 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -528,6 +528,7 @@ en:
528 title: "Events" 528 title: "Events"
529 create_event: "Create event" 529 create_event: "Create event"
530 edit_link: "edit" 530 edit_link: "edit"
531 search_placeholder: "Search title, description, location, tags…"
531 edit: 532 edit:
532 title: "Editing event" 533 title: "Editing event"
533 change_node: "Change node" 534 change_node: "Change node"
@@ -653,6 +654,7 @@ en:
653 index: 654 index:
654 title: "Assets" 655 title: "Assets"
655 create_asset: "Create asset" 656 create_asset: "Create asset"
657 search_placeholder: "Search name, creator, filename, type…"
656 show: 658 show:
657 thumbnail: "Thumbnail" 659 thumbnail: "Thumbnail"
658 attached_to: "Attached to" 660 attached_to: "Attached to"
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css
index e6f31709..b959597f 100644
--- a/public/stylesheets/admin.css
+++ b/public/stylesheets/admin.css
@@ -575,7 +575,7 @@ form.button_to svg {
575} 575}
576 576
577.button_row .action_button, 577.button_row .action_button,
578.node_search_form .action_button { 578.search_form .action_button {
579 margin-bottom: 0; 579 margin-bottom: 0;
580} 580}
581 581
@@ -1147,14 +1147,14 @@ form.button_to button[type="submit"] {
1147 margin-bottom: 0; 1147 margin-bottom: 0;
1148} 1148}
1149 1149
1150.node_search_form { 1150.search_form {
1151 display: flex; 1151 display: flex;
1152 align-items: center; 1152 align-items: center;
1153 gap: 0.5rem; 1153 gap: 0.5rem;
1154 margin-bottom: 0.75rem; 1154 margin-bottom: 0.75rem;
1155} 1155}
1156 1156
1157.node_search_form input[type=text] { 1157.search_form input[type=text] {
1158 padding: 4px 12px; 1158 padding: 4px 12px;
1159 box-sizing: border-box; 1159 box-sizing: border-box;
1160 height: 2.25rem; 1160 height: 2.25rem;
@@ -1163,7 +1163,7 @@ form.button_to button[type="submit"] {
1163 border-radius: 2px; 1163 border-radius: 2px;
1164} 1164}
1165 1165
1166.node_search_form .action_button { 1166.search_form .action_button {
1167 height: 2.25rem; 1167 height: 2.25rem;
1168 box-sizing: border-box; 1168 box-sizing: border-box;
1169} 1169}
diff --git a/test/models/asset_test.rb b/test/models/asset_test.rb
index 2677681b..be4be3b9 100644
--- a/test/models/asset_test.rb
+++ b/test/models/asset_test.rb
@@ -76,4 +76,31 @@ class AssetTest < ActiveSupport::TestCase
76 assert asset.valid?, asset.errors.full_messages.to_sentence 76 assert asset.valid?, asset.errors.full_messages.to_sentence
77 assert_equal "image/png", asset.upload_content_type 77 assert_equal "image/png", asset.upload_content_type
78 end 78 end
79
80 def build_asset(name:, creator: nil)
81 asset = Asset.new(:name => name, :creator => creator)
82 asset.upload = Rack::Test::UploadedFile.new(
83 file_fixture("test_document.pdf"), "application/pdf")
84 asset.save!
85 asset
86 end
87
88 test "editor_search matches across columns" do
89 pdf = build_asset(:name => "Offener Brief")
90 other = build_asset(:name => "Nothing distinctive", :creator => "Someone")
91
92 assert_includes Asset.editor_search("brief"), pdf
93 assert_not_includes Asset.editor_search("brief"), other
94 assert_includes Asset.editor_search("pdf"), pdf,
95 "upload_content_type should be searchable"
96 assert_includes Asset.editor_search("someone"), other,
97 "creator should be searchable"
98 end
99
100 test "editor_search ANDs multiple words" do
101 build_asset(:name => "Offener Brief")
102
103 assert_not_empty Asset.editor_search("offener brief")
104 assert_empty Asset.editor_search("offener zzzznomatch")
105 end
79end 106end
diff --git a/test/models/event_test.rb b/test/models/event_test.rb
index a5449a2a..49af8e38 100644
--- a/test/models/event_test.rb
+++ b/test/models/event_test.rb
@@ -178,4 +178,29 @@ class EventTest < ActiveSupport::TestCase
178 assert event.save 178 assert event.save
179 assert_equal event.start_time, event.occurrences.first&.start_time 179 assert_equal event.start_time, event.occurrences.first&.start_time
180 end 180 end
181
182 test "editor_search matches across columns" do
183 match = Event.create!(:title => "Chaosradio Spezial", :location => "Zentrale")
184 other = Event.create!(:title => "Nothing distinctive")
185
186 assert_includes Event.editor_search("chaosradio"), match
187 assert_not_includes Event.editor_search("chaosradio"), other
188 assert_includes Event.editor_search("zentrale"), match,
189 "location should be searchable"
190 end
191
192 test "editor_search ANDs multiple words" do
193 Event.create!(:title => "Chaosradio Spezial")
194
195 assert_not_empty Event.editor_search("chaosradio spezial")
196 assert_empty Event.editor_search("chaosradio zzzznomatch")
197 end
198
199 test "editor_search finds an event by its tag" do
200 event = Event.create!(:title => "Nothing distinctive")
201 event.tag_list.add("open-day")
202 event.save!
203
204 assert_includes Event.editor_search("open-day"), event
205 end
181end 206end