diff --git a/backend/app/controllers/tags_controller.rb b/backend/app/controllers/tags_controller.rb index 7c6feac..26c2fee 100644 --- a/backend/app/controllers/tags_controller.rb +++ b/backend/app/controllers/tags_controller.rb @@ -218,63 +218,16 @@ class TagsController < ApplicationController locale = resolve_locale! q = params[:q].to_s.strip.sub(/\Anot:/i, '') - prefix = "#{ ActiveRecord::Base.sanitize_sql_like(q) }%" + literal = ActiveRecord::Base.sanitize_sql_like(q) + nico = bool?(:nico, default: true) + present = bool?(:present, default: true) - with_nico = bool?(:nico, default: true) - present_only = bool?(:present, default: true) - - alias_rows = - TagName - .where(language_code: locale.language_code, primary_flg: false) - .where('name LIKE ?', prefix) - .pluck(:tag_id, :name) - - matched_alias_by_tag_id = { } - tag_ids = [] - alias_rows.each do |tag_id, alias_name| - tag_ids << tag_id - matched_alias_by_tag_id[tag_id] ||= alias_name + rows = autocomplete_rows(locale:, pattern: "#{ literal }%", nico:, present:) + if rows.length < 20 && q.present? + suffix_rows = autocomplete_rows(locale:, pattern: "%#{ literal }", nico:, present:) + rows = (rows + suffix_rows).uniq { [_1['category'], _1['id']] }.first(20) end - base = - Tag - .joins(:tag_names) - .preload(:tag_names, :materials, :deerjikists, :tag_descriptions, - parents: [:tag_names, :materials, :deerjikists, :tag_descriptions]) - .where(tag_names: { language_code: locale.language_code, primary_flg: true }, - deprecated_at: nil) - base = base.where('tags.post_count > 0') if present_only - - canonical_hit = base.where('tag_names.name LIKE ?', prefix) - - internal_tags = canonical_hit.or(base.where(id: tag_ids.uniq)) - - internal_rows = - internal_tags - .order(Arel.sql('tags.post_count DESC, tag_names.name')) - .limit(20) - .map { |tag| - TagRepr.base(tag, language_code: locale.language_code) - .merge(matched_alias: matched_alias_by_tag_id[tag.id]) - } - - return render json: internal_rows unless with_nico - - external_base = ExternalTag.all - external_base = external_base.where('post_count > 0') if present_only - - external_rows = - external_base - .where("CONCAT(platform, ':', name) LIKE ? OR name LIKE ?", prefix, prefix) - .order(post_count: :desc, name: :asc) - .limit(20) - .map { external_tag_json(_1) } - - rows = - (internal_rows + external_rows) - .sort_by { |row| [-row['post_count'], row['name'] || row[:name]] } - .first(20) - render json: rows end @@ -545,6 +498,59 @@ class TagsController < ApplicationController private + def autocomplete_rows locale:, pattern:, nico:, present: + alias_rows = + TagName + .where(language_code: locale.language_code, primary_flg: false) + .where('name LIKE ?', pattern) + .pluck(:tag_id, :name) + + matched_alias_by_tag_id = { } + tag_ids = [] + alias_rows.each do |tag_id, alias_name| + tag_ids << tag_id + matched_alias_by_tag_id[tag_id] ||= alias_name + end + + base = + Tag + .joins(:tag_names) + .preload(:tag_names, :materials, :deerjikists, :tag_descriptions, + parents: [:tag_names, :materials, :deerjikists, :tag_descriptions]) + .where(tag_names: { language_code: locale.language_code, primary_flg: true }, + deprecated_at: nil) + base = base.where('tags.post_count > 0') if present + + canonical_hit = base.where('tag_names.name LIKE ?', pattern) + + internal_tags = canonical_hit.or(base.where(id: tag_ids.uniq)) + + internal_rows = + internal_tags + .order(Arel.sql('tags.post_count DESC, tag_names.name')) + .limit(20) + .map { |tag| + TagRepr.base(tag, language_code: locale.language_code) + .merge(matched_alias: matched_alias_by_tag_id[tag.id]) + } + + return internal_rows unless nico + + external_base = ExternalTag.all + external_base = external_base.where('post_count > 0') if present + + external_rows = + external_base + .where("CONCAT(platform, ':', name) LIKE ? OR name LIKE ?", pattern, pattern) + .order(post_count: :desc, name: :asc) + .limit(20) + .map { external_tag_json(_1) } + + (internal_rows + external_rows) + .sort_by { |row| [-row['post_count'], row['name'] || row[:name]] } + .first(20) + end + def material_filter_param default: value = params[:material_filter].to_s.presence return default unless ['present', 'missing', 'any'].include?(value)