Files
danbooru/app/javascript/src/javascripts/autocomplete.js.erb

439 lines
13 KiB
Plaintext

import CurrentUser from './current_user'
let Autocomplete = {};
/* eslint-disable */
Autocomplete.METATAGS = <%= PostQueryBuilder::METATAGS.to_json.html_safe %>;
Autocomplete.TAG_CATEGORIES = <%= TagCategory.mapping.to_json.html_safe %>;
Autocomplete.ORDER_METATAGS = <%= PostQueryBuilder::ORDER_METATAGS.to_json.html_safe %>;
/* eslint-enable */
Autocomplete.TAG_PREFIXES = "-|~|" + Object.keys(Autocomplete.TAG_CATEGORIES).map(category => category + ":").join("|");
Autocomplete.METATAGS_REGEX = Autocomplete.METATAGS.concat(Object.keys(Autocomplete.TAG_CATEGORIES)).join("|");
Autocomplete.TERM_REGEX = new RegExp(`([-~]*)(?:(${Autocomplete.METATAGS_REGEX}):)?(\\S*)$`, "i");
Autocomplete.MAX_RESULTS = 10;
Autocomplete.initialize_all = function() {
if (CurrentUser.data("enable-auto-complete")) {
$.widget("ui.autocomplete", $.ui.autocomplete, {
options: {
delay: 0,
minLength: 1,
autoFocus: false,
focus: function() { return false; },
},
_create: function() {
this.element.on("keydown.Autocomplete.tab", null, "tab", Autocomplete.on_tab);
this._super();
},
_renderItem: Autocomplete.render_item,
search: function(value, event) {
if ($(this).data("ui-autocomplete")) {
$(this).data("ui-autocomplete").menu.bindings = $();
}
this._super(value, event);
},
});
this.initialize_tag_autocomplete();
this.initialize_mention_autocomplete($(".autocomplete-mentions textarea"));
this.initialize_fields($('[data-autocomplete="tag"]'), Autocomplete.tag_source);
this.initialize_fields($('[data-autocomplete="artist"]'), Autocomplete.artist_source);
this.initialize_fields($('[data-autocomplete="pool"]'), Autocomplete.pool_source);
this.initialize_fields($('[data-autocomplete="user"]'), Autocomplete.user_source);
this.initialize_fields($('[data-autocomplete="wiki-page"]'), Autocomplete.wiki_source);
this.initialize_fields($('[data-autocomplete="favorite-group"]'), Autocomplete.favorite_group_source);
this.initialize_fields($('[data-autocomplete="saved-search-label"]'), Autocomplete.saved_search_source);
}
}
Autocomplete.initialize_fields = function($fields, autocomplete) {
$fields.autocomplete({
source: async function(request, respond) {
let results = await autocomplete(request.term);
respond(results);
},
});
};
Autocomplete.initialize_mention_autocomplete = function($fields) {
$fields.autocomplete({
select: function(event, ui) {
Autocomplete.insert_completion(this, ui.item.value);
return false;
},
source: async function(req, resp) {
var cursor = this.element.get(0).selectionStart;
var name = null;
for (var i = cursor; i >= 1; --i) {
if (req.term[i - 1] === " ") {
return;
}
if (req.term[i - 1] === "@") {
if (i === 1 || /[ \r\n]/.test(req.term[i - 2])) {
name = req.term.substring(i, cursor);
break;
} else {
return;
}
}
}
if (name) {
let results = await Autocomplete.user_source(name, "@");
resp(results);
}
}
});
}
Autocomplete.initialize_tag_autocomplete = function() {
var $fields_multiple = $('[data-autocomplete="tag-query"], [data-autocomplete="tag-edit"]');
$fields_multiple.autocomplete({
select: function(event, ui) {
// Prevent Upload.initialize_enter_on_tags from running if the
// Enter key is used to select a tag from the autocomplete menu.
if (event.key === "Enter") {
event.stopImmediatePropagation();
}
Autocomplete.insert_completion(this, ui.item.value);
return false;
},
source: async function(req, resp) {
var query = Autocomplete.parse_query(req.term, this.element.get(0).selectionStart);
var metatag = query.metatag;
var term = query.term;
var results = [];
switch (metatag) {
case "order":
case "status":
case "rating":
case "locked":
case "child":
case "parent":
case "filetype":
case "disapproved":
case "embedded":
results = Autocomplete.static_metatag_source(term, metatag);
break;
case "user":
case "approver":
case "commenter":
case "comm":
case "noter":
case "noteupdater":
case "artcomm":
case "fav":
case "ordfav":
case "appealer":
case "flagger":
case "upvote":
case "downvote":
results = await Autocomplete.user_source(term, metatag + ":");
break;
case "pool":
case "ordpool":
results = await Autocomplete.pool_source(term, metatag + ":");
break;
case "favgroup":
results = await Autocomplete.favorite_group_source(term, metatag + ":", CurrentUser.data("id"));
break;
case "search":
results = await Autocomplete.saved_search_source(term, metatag + ":");
break;
case "tag":
results = await Autocomplete.tag_source(term);
break;
default:
results = [];
break;
}
resp(results);
}
});
}
Autocomplete.parse_query = function(text, caret) {
let before_caret_text = text.substring(0, caret);
let match = before_caret_text.match(Autocomplete.TERM_REGEX);
let operator = match[1];
let metatag = match[2] ? match[2].toLowerCase() : "tag";
let term = match[3];
if (metatag in Autocomplete.TAG_CATEGORIES) {
metatag = "tag";
}
return { operator: operator, metatag: metatag, term: term };
};
// Update the input field with the item currently focused in the
// autocomplete menu, then position the caret just after the inserted completion.
Autocomplete.insert_completion = function(input, completion) {
// Trim all whitespace (tabs, spaces) except for line returns
var before_caret_text = input.value.substring(0, input.selectionStart).replace(/^[ \t]+|[ \t]+$/gm, "");
var after_caret_text = input.value.substring(input.selectionStart).replace(/^[ \t]+|[ \t]+$/gm, "");
var regexp = new RegExp("(" + Autocomplete.TAG_PREFIXES + ")?\\S+$", "g");
before_caret_text = before_caret_text.replace(regexp, "$1") + completion + " ";
input.value = before_caret_text + after_caret_text;
input.selectionStart = input.selectionEnd = before_caret_text.length;
};
// If we press tab while the autocomplete menu is open but nothing is
// focused, complete the first item and close the menu.
Autocomplete.on_tab = function(event) {
var input = this;
var autocomplete = $(input).autocomplete("instance");
var $autocomplete_menu = autocomplete.menu.element;
if (!$autocomplete_menu.is(":visible")) {
return;
}
if ($autocomplete_menu.has(".ui-state-active").length === 0) {
var $first_item = $autocomplete_menu.find(".ui-menu-item").first();
var completion = $first_item.data().uiAutocompleteItem.value;
Autocomplete.insert_completion(input, completion);
autocomplete.close();
}
// Prevent the tab key from moving focus to the next element.
event.preventDefault();
};
Autocomplete.render_item = function(list, item) {
var $link = $("<a/>");
$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 = $("<span/>").html(" &rarr; ").addClass("autocomplete-arrow");
var antecedent_element = $("<span/>").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 = $("<span/>").addClass("post-count").css("float", "right").text(count);
$link.append($post_count);
}
if (item.type === "tag") {
$link.addClass("tag-type-" + item.category);
} else if (item.type === "user") {
var level_class = "user-" + item.level.toLowerCase();
$link.addClass(level_class);
if (CurrentUser.data("style-usernames")) {
$link.addClass("with-style");
}
} else if (item.type === "pool") {
$link.addClass("pool-category-" + item.category);
}
var $menu_item = $("<div/>").append($link);
var $list_item = $("<li/>").data("item.autocomplete", item).append($menu_item);
var data_attributes = ["type", "source", "antecedent", "value", "category", "post_count", "weight"];
data_attributes.forEach(attr => {
$list_item.attr(`data-autocomplete-${attr.replace(/_/g, "-")}`, item[attr]);
});
return $list_item.appendTo(list);
};
Autocomplete.static_metatags = {
order: Autocomplete.ORDER_METATAGS,
status: [
"any", "deleted", "active", "pending", "flagged", "banned", "modqueue", "unmoderated"
],
rating: [
"safe", "questionable", "explicit"
],
locked: [
"rating", "note", "status"
],
embedded: [
"true", "false"
],
child: [
"any", "none"
],
parent: [
"any", "none"
],
filetype: [
"jpg", "png", "gif", "swf", "zip", "webm", "mp4"
],
disapproved: [
"disinterest", "poor_quality", "breaks_rules"
]
}
Autocomplete.static_metatag_source = function(term, metatag) {
var sub_metatags = this.static_metatags[metatag];
var matches = sub_metatags.filter(sub_metatag => sub_metatag.startsWith(term.toLowerCase()));
matches = matches.map(sub_metatag => `${metatag}:${sub_metatag}`).sort().slice(0, Autocomplete.MAX_RESULTS);
return matches;
}
Autocomplete.tag_source = async function(term) {
if (term === "") {
return [];
}
let tags = await $.getJSON("/tags/autocomplete", {
"search[name_matches]": term,
"limit": Autocomplete.MAX_RESULTS,
"expiry": 7
});
return tags.map(function(tag) {
return {
type: "tag",
label: tag.name.replace(/_/g, " "),
antecedent: tag.antecedent_name,
value: tag.name,
category: tag.category,
source: tag.source,
weight: tag.weight,
post_count: tag.post_count
};
});
}
Autocomplete.artist_source = async function(term) {
let artists = await $.getJSON("/artists", {
"search[name_like]": term.trim().replace(/\s+/g, "_") + "*",
"search[is_deleted]": false,
"search[order]": "post_count",
"limit": Autocomplete.MAX_RESULTS,
"expiry": 7
});
return artists.map(function(artist) {
return {
type: "tag",
label: artist.name.replace(/_/g, " "),
value: artist.name,
category: Autocomplete.TAG_CATEGORIES.artist,
};
});
};
Autocomplete.wiki_source = async function(term) {
let wiki_pages = await $.getJSON("/wiki_pages", {
"search[title_normalize]": term + "*",
"search[hide_deleted]": "Yes",
"search[order]": "post_count",
"limit": Autocomplete.MAX_RESULTS,
"expiry": 7
});
return wiki_pages.map(function(wiki_page) {
return {
type: "tag",
label: wiki_page.title.replace(/_/g, " "),
value: wiki_page.title,
category: wiki_page.category_name
};
});
};
Autocomplete.user_source = async function(term, prefix = "") {
let users = await $.getJSON("/users", {
"search[order]": "post_upload_count",
"search[current_user_first]": "true",
"search[name_matches]": term + "*",
"limit": Autocomplete.MAX_RESULTS
});
return users.map(function(user) {
return {
type: "user",
label: user.name.replace(/_/g, " "),
value: prefix + user.name,
level: user.level_string
};
});
};
Autocomplete.pool_source = async function(term, prefix = "") {
let pools = await $.getJSON("/pools", {
"search[name_matches]": term,
"search[is_deleted]": false,
"search[order]": "post_count",
"limit": Autocomplete.MAX_RESULTS
});
return pools.map(function(pool) {
return {
type: "pool",
label: pool.name.replace(/_/g, " "),
value: prefix + pool.name,
post_count: pool.post_count,
category: pool.category
};
});
};
Autocomplete.favorite_group_source = async function(term, prefix = "", creator_id = null) {
let favgroups = await $.getJSON("/favorite_groups", {
"search[creator_id]": creator_id,
"search[name_matches]": term,
"limit": Autocomplete.MAX_RESULTS
});
return favgroups.map(function(favgroup) {
return {
label: favgroup.name.replace(/_/g, " "),
value: prefix + favgroup.name,
post_count: favgroup.post_count
};
});
};
Autocomplete.saved_search_source = async function(term, prefix = "") {
let labels = await $.getJSON("/saved_searches/labels", {
"search[label]": term + "*",
"limit": Autocomplete.MAX_RESULTS
});
return labels.map(function(label) {
return {
label: label.replace(/_/g, " "),
value: prefix + label,
};
});
}
$(document).ready(function() {
Autocomplete.initialize_all();
});
export default Autocomplete;