diff --git a/app/components/autocomplete_component.rb b/app/components/autocomplete_component.rb
new file mode 100644
index 000000000..7ab304bb6
--- /dev/null
+++ b/app/components/autocomplete_component.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class AutocompleteComponent < ApplicationComponent
+ attr_reader :autocomplete_service
+
+ delegate :humanized_number, to: :helpers
+ delegate :autocomplete_results, to: :autocomplete_service
+
+ def initialize(autocomplete_service:)
+ @autocomplete_service = autocomplete_service
+ end
+
+ def link_to_result(result, &block)
+ case result.type
+ when "user"
+ link_to user_path(result.id), class: "user-#{result.level}", "@click.prevent": "", &block
+ when "pool"
+ link_to pool_path(result.id), class: "pool-category-#{result.category}", "@click.prevent": "", &block
+ else
+ link_to posts_path(tags: result.value), class: "tag-type-#{result.category}", "@click.prevent": "", &block
+ end
+ end
+end
diff --git a/app/components/autocomplete_component/autocomplete_component.html.erb b/app/components/autocomplete_component/autocomplete_component.html.erb
new file mode 100644
index 000000000..992742dcc
--- /dev/null
+++ b/app/components/autocomplete_component/autocomplete_component.html.erb
@@ -0,0 +1,20 @@
+
+ <% autocomplete_results.each do |result| %>
+ <%= tag.li class: "ui-menu-item", "data-autocomplete-type": result.type, "data-autocomplete-antecedent": result.antecedent, "data-autocomplete-value": result.value, "data-autocomplete-category": result.category, "data-autocomplete-post-count": result.post_count do %>
+
+ <% end %>
+ <% end %>
+
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 95df10f69..9ce5628de 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class AutocompleteController < ApplicationController
- respond_to :xml, :json
+ respond_to :html, :xml, :json
def index
@query = params.dig(:search, :query)
@@ -14,6 +14,6 @@ class AutocompleteController < ApplicationController
@public = @autocomplete.cache_publicly?
expires_in @expires_in, public: @public unless response.cache_control.present?
- respond_with(@results)
+ respond_with(@results, layout: false)
end
end
diff --git a/app/javascript/src/javascripts/autocomplete.js b/app/javascript/src/javascripts/autocomplete.js
index 36b1ae62f..4ae4c3e3d 100644
--- a/app/javascript/src/javascripts/autocomplete.js
+++ b/app/javascript/src/javascripts/autocomplete.js
@@ -1,5 +1,6 @@
let Autocomplete = {};
+Autocomplete.VERSION = 1; // This should be bumped whenever the /autocomplete API changes in order to invalid client caches.
Autocomplete.MAX_RESULTS = 10;
Autocomplete.initialize_all = function() {
@@ -138,64 +139,28 @@ Autocomplete.on_tab = function(event) {
};
Autocomplete.render_item = function(list, item) {
- var $link = $("");
- $link.text(item.label);
- $link.attr("href", "/posts?tags=" + encodeURIComponent(item.value));
- $link.on("click.danbooru", function(e) {
- e.preventDefault();
- });
-
- if (item.antecedent) {
- var antecedent = item.antecedent.replace(/_/g, " ");
- var arrow = $("").html(" → ").addClass("autocomplete-arrow");
- var antecedent_element = $("").text(antecedent).addClass("autocomplete-antecedent");
- $link.prepend([
- antecedent_element,
- arrow
- ]);
- }
-
- if (item.post_count !== undefined) {
- var count = item.post_count;
-
- if (count >= 1000) {
- count = Math.floor(count / 1000) + "k";
- }
-
- var $post_count = $("").addClass("post-count").css("float", "right").text(count);
- $link.append($post_count);
- }
-
- if (/^tag/.test(item.type)) {
- $link.addClass("tag-type-" + item.category);
- } else if (item.type === "user") {
- var level_class = "user-" + item.level.toLowerCase();
- $link.addClass(level_class);
- } else if (item.type === "pool") {
- $link.addClass("pool-category-" + item.category);
- }
-
- var $menu_item = $("").append($link);
- var $list_item = $("").data("item.autocomplete", item).append($menu_item);
-
- var data_attributes = ["type", "antecedent", "value", "category", "post_count"];
- data_attributes.forEach(attr => {
- $list_item.attr(`data-autocomplete-${attr.replace(/_/g, "-")}`, item[attr]);
- });
-
- return $list_item.appendTo(list);
+ item.html.data("ui-autocomplete-item", item);
+ return list.append(item.html);
};
-Autocomplete.autocomplete_source = function(query, type) {
+Autocomplete.autocomplete_source = async function(query, type) {
if (query === "") {
return [];
}
- return $.getJSON("/autocomplete.json", {
+ let html = await $.get("/autocomplete", {
"search[query]": query,
"search[type]": type,
+ "version": Autocomplete.VERSION,
"limit": Autocomplete.MAX_RESULTS
});
+
+ let items = $(html).find("li").toArray().map(item => {
+ let $item = $(item);
+ return { value: $item.attr("data-autocomplete-value"), html: $item };
+ });
+
+ return items;
}
Autocomplete.tag_prefixes = function() {
diff --git a/app/logical/autocomplete_service.rb b/app/logical/autocomplete_service.rb
index a713f6e98..424a00dd3 100644
--- a/app/logical/autocomplete_service.rb
+++ b/app/logical/autocomplete_service.rb
@@ -48,6 +48,7 @@ class AutocompleteService
# @return [Array] the autocomplete results
def autocomplete_results
return [] if !enabled?
+ return autocomplete_opensearch(query) if type == :opensearch
case type
when :tag_query
@@ -68,11 +69,9 @@ class AutocompleteService
autocomplete_favorite_group(query)
when :saved_search_label
autocomplete_saved_search_label(query)
- when :opensearch
- autocomplete_opensearch(query)
else
[]
- end
+ end.map { |result| OpenStruct.new(result) }
end
# Complete a tag search (a regular tag or a metatag)
@@ -238,7 +237,7 @@ class AutocompleteService
pools = Pool.undeleted.name_matches(string).search(order: "post_count").limit(limit)
pools.map do |pool|
- { type: "pool", label: pool.pretty_name, value: pool.name, post_count: pool.post_count, category: pool.category }
+ { type: "pool", label: pool.pretty_name, value: pool.name, id: pool.id, post_count: pool.post_count, category: pool.category }
end
end
@@ -298,7 +297,7 @@ class AutocompleteService
users = User.search(name_matches: string, current_user_first: true, order: "post_upload_count").limit(limit)
users.map do |user|
- { type: "user", label: user.pretty_name, value: user.name, level: user.level_string }
+ { type: "user", label: user.pretty_name, value: user.name, id: user.id, level: user.level_string.downcase }
end
end
diff --git a/app/views/autocomplete/index.html.erb b/app/views/autocomplete/index.html.erb
new file mode 100644
index 000000000..d7917f4d9
--- /dev/null
+++ b/app/views/autocomplete/index.html.erb
@@ -0,0 +1 @@
+<%= render AutocompleteComponent.new(autocomplete_service: @autocomplete) %>
diff --git a/test/functional/autocomplete_controller_test.rb b/test/functional/autocomplete_controller_test.rb
index b8d0257fb..3529c7c30 100644
--- a/test/functional/autocomplete_controller_test.rb
+++ b/test/functional/autocomplete_controller_test.rb
@@ -2,10 +2,10 @@ require "test_helper"
class AutocompleteControllerTest < ActionDispatch::IntegrationTest
def autocomplete(query, type)
- get autocomplete_index_path(search: { query: query, type: type }), as: :json
+ get autocomplete_index_path(search: { query: query, type: type })
assert_response :success
- response.parsed_body.map { |result| result["value"] }
+ response.parsed_body.css("li").map { |html| html["data-autocomplete-value"] }
end
def assert_autocomplete_equals(expected_value, query, type)
@@ -35,6 +35,24 @@ class AutocompleteControllerTest < ActionDispatch::IntegrationTest
assert_autocomplete_equals(["rating:sensitive"], "-rating:s", "tag_query")
end
+ should "work for an aliased tag" do
+ create(:tag_alias, antecedent_name: "oc", consequent_name: "original")
+
+ assert_autocomplete_equals(["original"], "oc", "tag_query")
+ end
+
+ should "work for the user: metatag" do
+ create(:user, name: "foobar")
+
+ assert_autocomplete_equals(["user:foobar"], "user:foo", "tag_query")
+ end
+
+ should "work for the pool: metatag" do
+ as(create(:user)) { create(:pool, name: "foobar") }
+
+ assert_autocomplete_equals(["pool:foobar"], "pool:foo", "tag_query")
+ end
+
should "work for a missing type" do
get autocomplete_index_path(search: { query: "azur" }), as: :json
@@ -48,7 +66,7 @@ class AutocompleteControllerTest < ActionDispatch::IntegrationTest
end
should "not set session cookies when the response is publicly cached" do
- get autocomplete_index_path(search: { query: "azur", type: "tag_query" }), as: :json
+ get autocomplete_index_path(search: { query: "azur", type: "tag_query" })
assert_response :success
assert_equal(true, response.cache_control[:public])