Files
danbooru/app/models/wiki_page.rb
evazion adc1c2c2cc autocomplete: refactor javascript to use /autocomplete endpoint.
This refactors the autocomplete Javascript to use a single dedicated
/autocomplete.json endpoint instead of a bunch of separate endpoints.

This simplifies the autocomplete Javascript by making it so that instead
of calling a different endpoint for each type of query (for users, wiki
pages, pools, artists, etc), then having to parse the results of each
call to get the data we need, we can call a single endpoint that returns
exactly what we need.

This also means we don't have to parse searches clientside in order to
autocomplete metatags. Instead we can just pass the search term to the
server and let it parse the search, which is easy to do serverside.

Finally, this makes autocomplete easier to test, and it makes it easier
to add more sophisticated autocomplete behavior, since most of the logic
lives serverside.
2020-12-13 00:45:22 -06:00

259 lines
7.1 KiB
Ruby

class WikiPage < ApplicationRecord
class RevertError < StandardError; end
META_WIKIS = ["list_of_", "tag_group:", "pool_group:", "howto:", "about:", "help:", "template:"]
before_save :normalize_title
before_save :normalize_other_names
before_save :update_dtext_links, if: :dtext_links_changed?
after_save :create_version
validates_uniqueness_of :title, :case_sensitive => false
validates_presence_of :title
validates_presence_of :body, :unless => -> { is_deleted? || other_names.present? }
validate :validate_rename
array_attribute :other_names
has_one :tag, :foreign_key => "name", :primary_key => "title"
has_one :artist, -> { active }, foreign_key: "name", primary_key: "title"
has_many :versions, -> {order("wiki_page_versions.id ASC")}, :class_name => "WikiPageVersion", :dependent => :destroy
has_many :dtext_links, as: :model, dependent: :destroy
deletable
module SearchMethods
def find_by_id_or_title(id)
if id =~ /\A\d+\z/
[find_by_id(id), :id]
else
[find_by_title(normalize_title(id)), :title]
end
end
def titled(title)
where(title: normalize_title(title))
end
def title_matches(title)
where_like(:title, normalize_title(title))
end
def other_names_include(name)
name = normalize_other_name(name)
subquery = WikiPage.from("unnest(other_names) AS other_name").where_iequals("other_name", name)
where(id: subquery)
end
def other_names_match(name)
if name =~ /\*/
subquery = WikiPage.from("unnest(other_names) AS other_name").where_ilike("other_name", name)
where(id: subquery)
else
other_names_include(name)
end
end
def linked_to(title)
where(dtext_links: DtextLink.wiki_page.wiki_link.where(link_target: normalize_title(title)))
end
def not_linked_to(title)
where.not(dtext_links: DtextLink.wiki_page.wiki_link.where(link_target: normalize_title(title)))
end
def default_order
order(updated_at: :desc)
end
def search(params = {})
q = super
q = q.search_attributes(params, :is_locked, :is_deleted, :body, :title, :other_names)
q = q.text_attribute_matches(:body, params[:body_matches], index_column: :body_index, ts_config: "danbooru")
if params[:title_normalize].present?
q = q.where_like(:title, normalize_title(params[:title_normalize]))
end
if params[:other_names_match].present?
q = q.other_names_match(params[:other_names_match])
end
if params[:linked_to].present?
q = q.linked_to(params[:linked_to])
end
if params[:not_linked_to].present?
q = q.not_linked_to(params[:not_linked_to])
end
if params[:hide_deleted].to_s.truthy?
q = q.where("is_deleted = false")
end
if params[:other_names_present].to_s.truthy?
q = q.where("other_names is not null and other_names != '{}'")
elsif params[:other_names_present].to_s.falsy?
q = q.where("other_names is null or other_names = '{}'")
end
case params[:order]
when "title"
q = q.order("title")
when "post_count"
q = q.includes(:tag).order("tags.post_count desc nulls last").references(:tags)
else
q = q.apply_default_order(params)
end
q
end
end
extend SearchMethods
def validate_rename
return unless title_changed?
tag_was = Tag.find_by_name(Tag.normalize_name(title_was))
if tag_was.present? && !tag_was.empty?
warnings[:base] << %!Warning: {{#{title_was}}} still has #{tag_was.post_count} #{"post".pluralize(tag_was.post_count)}. Be sure to move the posts!
end
broken_wikis = WikiPage.linked_to(title_was)
if broken_wikis.count > 0
broken_wiki_search = Rails.application.routes.url_helpers.wiki_pages_path(search: { linked_to: title_was })
warnings[:base] << %!Warning: [[#{title_was}]] is still linked from "#{broken_wikis.count} #{"other wiki page".pluralize(broken_wikis.count)}":[#{broken_wiki_search}]. Update #{(broken_wikis.count > 1) ? "these wikis" : "this wiki"} to link to [[#{title}]] instead!
end
end
def revert_to(version)
if id != version.wiki_page_id
raise RevertError.new("You cannot revert to a previous version of another wiki page.")
end
self.title = version.title
self.body = version.body
self.is_locked = version.is_locked
self.other_names = version.other_names
end
def revert_to!(version)
revert_to(version)
save!
end
def self.normalize_title(title)
return if title.blank?
title.downcase.delete_prefix("~").gsub(/[[:space:]]+/, "_").gsub(/__/, "_").gsub(/\A_|_\z/, "")
end
def normalize_title
self.title = WikiPage.normalize_title(title)
end
def normalize_other_names
self.other_names = other_names.map { |name| WikiPage.normalize_other_name(name) }.uniq
end
def self.normalize_other_name(name)
name.unicode_normalize(:nfkc).gsub(/[[:space:]]+/, " ").strip.tr(" ", "_")
end
def category_name
tag&.category
end
def pretty_title
title.tr("_", " ")
end
def self.is_meta_wiki?(title)
title.present? && title.starts_with?(*META_WIKIS)
end
def is_meta_wiki?
WikiPage.is_meta_wiki?(title)
end
def wiki_page_changed?
saved_change_to_title? || saved_change_to_body? || saved_change_to_is_locked? || saved_change_to_is_deleted? || saved_change_to_other_names?
end
def merge_version
prev = versions.last
prev.update(title: title, body: body, is_locked: is_locked, is_deleted: is_deleted, other_names: other_names)
end
def merge_version?
prev = versions.last
prev && prev.updater_id == CurrentUser.id && prev.updated_at > 1.hour.ago
end
def create_new_version
versions.create(
:updater_id => CurrentUser.id,
:updater_ip_addr => CurrentUser.ip_addr,
:title => title,
:body => body,
:is_locked => is_locked,
:is_deleted => is_deleted,
:other_names => other_names
)
end
def create_version
if wiki_page_changed?
if merge_version?
merge_version
else
create_new_version
end
end
end
def dtext_links_changed?
body_changed? && DText.dtext_links_differ?(body, body_was)
end
def update_dtext_links
self.dtext_links = DtextLink.new_from_dtext(body)
end
def tags
titles = DText.parse_wiki_titles(body).uniq
tags = Tag.nonempty.where(name: titles).pluck(:name)
tags += TagAlias.active.where(antecedent_name: titles).pluck(:antecedent_name)
TagAlias.to_aliased(titles & tags)
end
def self.rewrite_wiki_links!(old_name, new_name)
broken_wikis = WikiPage.linked_to(old_name)
broken_wikis.each do |wiki|
wiki.lock!
wiki.body = DText.rewrite_wiki_links(wiki.body, old_name, new_name)
wiki.save!
end
end
def to_param
if title =~ /\A\d+\z/
"~#{title}"
else
title
end
end
def self.model_restriction(table)
super.where(table[:is_deleted].eq(false))
end
def self.searchable_includes
[:tag, :artist, :dtext_links]
end
def self.available_includes
[:tag, :artist, :dtext_links]
end
end