Files
danbooru/app/assets/javascripts/autocomplete.js.erb

567 lines
17 KiB
Plaintext

(function() {
Danbooru.Autocomplete = {};
Danbooru.Autocomplete.AUTOCOMPLETE_VERSION = 1;
//Just under 5MB of 16-bit characters
Danbooru.Autocomplete.MAX_STORAGE_SIZE = 2500000;
Danbooru.Autocomplete.PREFIXES = /^(-|~|<%= TagCategory.mapping.keys.map {|category| category + ':'}.join('|') %>)(.*)$/i;
Danbooru.Autocomplete.METATAGS = /^(<%= Tag::METATAGS %>):(.*)$/i;
Danbooru.Autocomplete.initialize_all = function() {
if (Danbooru.meta("enable-auto-complete") === "true") {
$.widget("ui.autocomplete", $.ui.autocomplete, {
options: {
delay: 100,
minLength: 1,
autoFocus: false,
focus: function() { return false; },
},
_create: function() {
this.element.on("keydown.danbooru.autocomplete.tab", null, "tab", Danbooru.Autocomplete.on_tab);
this._super();
},
_renderItem: Danbooru.Autocomplete.render_item,
});
Danbooru.Autocomplete.enable_local_storage = this.test_local_storage();
this.initialize_tag_autocomplete();
this.initialize_mention_autocomplete($(".autocomplete-mentions textarea"));
this.initialize_artist_autocomplete($('[data-autocomplete="artist"]'));
this.initialize_pool_autocomplete($('[data-autocomplete="pool"]'));
this.initialize_wiki_autocomplete($('[data-autocomplete="wiki-page"]'));
this.prune_local_storage();
}
}
Danbooru.Autocomplete.test_local_storage = function() {
try {
$.localStorage.set("test", "test");
$.localStorage.remove("test");
return true;
} catch(e) {
return false;
}
}
Danbooru.Autocomplete.prune_local_storage = function() {
if (this.enable_local_storage) {
var cached_autocomplete_version = $.localStorage.get("danbooru-autocomplete-version");
var current_cache_size = Object.keys(localStorage).reduce( function(total, key) { return total + localStorage[key].length; }, 0);
if (cached_autocomplete_version !== this.AUTOCOMPLETE_VERSION || current_cache_size > this.MAX_STORAGE_SIZE) {
$.each(Object.keys(localStorage), function(i, key) {
if (key.substr(0, 3) === "ac-") {
$.localStorage.remove(key);
}
});
$.localStorage.set("danbooru-autocomplete-version", this.AUTOCOMPLETE_VERSION);
}
}
}
Danbooru.Autocomplete.initialize_mention_autocomplete = function($fields) {
$fields.autocomplete({
search: function() {
$(this).data("ui-autocomplete").menu.bindings = $();
},
select: function(event, ui) {
Danbooru.Autocomplete.insert_completion(this, ui.item.value);
return false;
},
source: function(req, resp) {
var cursor = this.element.get(0).selectionStart;
var i;
var name = null;
for (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) {
Danbooru.Autocomplete.user_source(name, resp, "@");
}
return;
}
});
}
Danbooru.Autocomplete.initialize_tag_autocomplete = function() {
var $fields_multiple = $('[data-autocomplete="tag-query"], [data-autocomplete="tag-edit"]');
var $fields_single = $('[data-autocomplete="tag"]');
$fields_multiple.autocomplete({
search: function() {
if ($(this).data("ui-autocomplete")) {
$(this).data("ui-autocomplete").menu.bindings = $();
}
},
select: function(event, ui) {
// Prevent Danbooru.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();
}
Danbooru.Autocomplete.insert_completion(this, ui.item.value);
return false;
},
source: function(req, resp) {
var query = Danbooru.Autocomplete.parse_query(req.term, this.element.get(0).selectionStart);
var metatag = query.metatag;
var term = query.term;
if (!metatag && !term) {
this.close();
return;
}
switch(metatag) {
case "md5":
case "width":
case "height":
case "mpixels":
case "ratio":
case "score":
case "favcount":
case "filesize":
case "source":
case "id":
case "date":
case "age":
case "limit":
case "tagcount":
case "pixiv_id":
case "pixiv":
<% TagCategory.short_name_list.each do |category| %>
case "<%= category %>tags":
<% end %>
resp([]);
return;
case "order":
case "status":
case "rating":
case "locked":
case "child":
case "parent":
case "filetype":
Danbooru.Autocomplete.static_metatag_source(term, resp, metatag);
return;
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":
Danbooru.Autocomplete.user_source(term, resp, metatag);
break;
case "pool":
case "ordpool":
Danbooru.Autocomplete.pool_source(term, resp, metatag);
break;
case "favgroup":
Danbooru.Autocomplete.favorite_group_source(term, resp, metatag);
break;
case "search":
Danbooru.Autocomplete.saved_search_source(term, resp);
break;
default:
Danbooru.Autocomplete.normal_source(term, resp);
break;
}
}
});
$fields_single.autocomplete({
search: function() {
$(this).data("ui-autocomplete").menu.bindings = $();
},
source: function(req, resp) {
Danbooru.Autocomplete.normal_source(req.term, resp);
}
});
}
Danbooru.Autocomplete.initialize_artist_autocomplete = function($fields) {
$fields.autocomplete({
search: function() {
$(this).data("ui-autocomplete").menu.bindings = $();
},
source: function(req, resp) {
$.ajax({
url: "/artists.json",
data: {
"search[name]": req.term + "*",
"search[is_active]": true,
"search[order]": "post_count",
"limit": 10
},
method: "get",
success: function(data) {
resp($.map(data, function(artist) {
return {
type: "tag",
label: artist.name.replace(/_/g, " "),
value: artist.name,
category: <%= Tag.categories.artist %>,
};
}));
}
});
}
});
};
Danbooru.Autocomplete.initialize_pool_autocomplete = function($fields) {
$fields.autocomplete({
search: function() {
$(this).data("ui-autocomplete").menu.bindings = $();
},
source: function(req, resp) {
Danbooru.Autocomplete.pool_source(req.term, resp);
},
});
};
Danbooru.Autocomplete.initialize_wiki_autocomplete = function($fields) {
$fields.autocomplete({
search: function() {
$(this).data("ui-autocomplete").menu.bindings = $();
},
source: function(req, resp) {
$.ajax({
url: "/wiki_pages.json",
data: {
"search[title]": req.term + "*",
"search[hide_deleted]": "Yes",
"search[order]": "post_count",
"limit": 10
},
method: "get",
success: function(data) {
resp($.map(data, function(wiki_page) {
return {
type: "tag",
label: wiki_page.title.replace(/_/g, " "),
value: wiki_page.title,
category: wiki_page.category_name
};
}));
}
});
}
});
};
Danbooru.Autocomplete.normal_source = function(term, resp) {
var key = "ac-" + term.replace(/\./g,'\uFFFF');
if (this.enable_local_storage) {
var cached = $.localStorage.get(key);
if (cached) {
if (Date.parse(cached.expires) < new Date().getTime()) {
$.localStorage.remove(key);
} else {
resp(cached.value);
return;
}
}
}
$.ajax({
url: "/tags/autocomplete.json",
data: {
"search[name_matches]": term
},
method: "get",
success: function(data) {
var d = $.map(data, function(tag) {
return {
type: "tag",
label: tag.name.replace(/_/g, " "),
antecedent: tag.antecedent_name,
value: tag.name,
category: tag.category,
post_count: tag.post_count
};
});
if (Danbooru.Autocomplete.enable_local_storage) {
var expiry = new Date();
expiry.setDate(expiry.getDate() + 7);
$.localStorage.set(key, {"value": d, "expires": expiry});
}
resp(d);
}
});
}
Danbooru.Autocomplete.parse_query = function(text, caret) {
var metatag = "";
var term = "";
var before_caret_text = text.substring(0, caret);
var match = before_caret_text.match(/\S+$/g);
if (match) {
term = match[0];
} else {
return {};
}
if (match = term.match(Danbooru.Autocomplete.PREFIXES)) {
metatag = match[1].toLowerCase();
term = match[2];
}
if (match = term.match(Danbooru.Autocomplete.METATAGS)) {
metatag = match[1].toLowerCase();
term = match[2];
}
return { 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.
Danbooru.Autocomplete.insert_completion = function(input, completion) {
var before_caret_text = input.value.substring(0, input.selectionStart).trim();
var after_caret_text = input.value.substring(input.selectionStart).trim();
var prefixes = "-|~|" + "<%= TagCategory.mapping.keys.map {|category| category + ':'}.join('|') %>";
var regexp = new RegExp("(" + 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.
Danbooru.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;
Danbooru.Autocomplete.insert_completion(input, completion);
autocomplete.close();
}
// Prevent the tab key from moving focus to the next element.
event.preventDefault();
};
Danbooru.Autocomplete.render_item = function(list, item) {
var $link = $("<a/>");
var $menu_item = $("<div/>").append($link);
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.append(antecedent_element);
$link.append(arrow);
}
$link.append(document.createTextNode(item.label));
$link.attr("href", "/posts?tags=" + encodeURIComponent(item.value));
$link.click(function(e) {
e.preventDefault();
});
if (item.post_count !== undefined) {
var count;
if (item.post_count >= 1000) {
count = Math.floor(item.post_count / 1000) + "k";
} else {
count = item.post_count;
}
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 (Danbooru.meta("style-usernames") === "true") {
$link.addClass("with-style");
}
} else if (item.type === "pool") {
$link.addClass("pool-category-" + item.category);
}
return $("<li/>").data("item.autocomplete", item).append($menu_item).appendTo(list);
};
Danbooru.Autocomplete.static_metatags = {
order: [
"id", "id_desc",
"score", "score_asc",
"favcount", "favcount_asc",
"created_at", "created_at_asc",
"change", "change_asc",
"comment", "comment_asc",
"comment_bumped", "comment_bumped_asc",
"note", "note_asc",
"artcomm", "artcomm_asc",
"mpixels", "mpixels_asc",
"portrait", "landscape",
"filesize", "filesize_asc",
"tagcount", "tagcount_asc",
"rank",
"random",
"custom"
].concat(<%= TagCategory.short_name_list.map {|category| [category + "tags", category + "tags_asc"]}.flatten %>),
status: [
"any", "deleted", "active", "pending", "flagged", "banned"
],
rating: [
"safe", "questionable", "explicit"
],
locked: [
"rating", "note", "status"
],
child: [
"any", "none"
],
parent: [
"any", "none"
],
filetype: [
"jpg", "png", "gif", "swf", "zip", "webm", "mp4"
],
}
Danbooru.Autocomplete.static_metatag_source = function(term, resp, metatag) {
var sub_metatags = this.static_metatags[metatag];
var regexp = new RegExp("^" + $.ui.autocomplete.escapeRegex(term), "i");
var matches = $.grep(sub_metatags, function (sub_metatag) {
return regexp.test(sub_metatag);
});
resp($.map(matches, function(sub_metatag) {
return metatag + ":" + sub_metatag;
}));
}
Danbooru.Autocomplete.user_source = function(term, resp, metatag) {
$.ajax({
url: "/users.json",
data: {
"search[order]": "post_upload_count",
"search[current_user_first]": "true",
"search[name_matches]": term + "*",
"limit": 10
},
method: "get",
success: function(data) {
var prefix;
var display_name;
if (metatag === "@") {
prefix = "@";
display_name = function(name) {return name;};
} else {
prefix = metatag + ":";
display_name = function(name) {return name.replace(/_/g, " ");};
}
resp($.map(data, function(user) {
return {
type: "user",
label: display_name(user.name),
value: prefix + user.name,
level: user.level_string
};
}));
}
});
}
Danbooru.Autocomplete.pool_source = function(term, resp, metatag) {
$.ajax({
url: "/pools.json",
data: {
"search[order]": "post_count",
"search[name_matches]": term,
"limit": 10
},
method: "get",
success: function(data) {
resp($.map(data, function(pool) {
return {
type: "pool",
label: pool.name.replace(/_/g, " "),
value: (metatag ? (metatag + ":" + pool.name) : pool.name),
post_count: pool.post_count,
category: pool.category
};
}));
}
});
}
Danbooru.Autocomplete.favorite_group_source = function(term, resp, metatag) {
$.ajax({
url: "/favorite_groups.json",
data: {
"search[name_matches]": term,
"limit": 10
},
method: "get",
success: function(data) {
resp($.map(data, function(favgroup) {
return {
label: favgroup.name.replace(/_/g, " "),
value: metatag + ":" + favgroup.name,
post_count: favgroup.post_count
};
}));
}
});
}
Danbooru.Autocomplete.saved_search_source = function(term, resp) {
return Danbooru.SavedSearch.labels(term).then(function(labels) {
resp(labels.map(function(label) {
return {
label: label.replace(/_/g, " "),
value: "search:" + label,
};
}));
});
}
})();
$(document).ready(function() {
Danbooru.Autocomplete.initialize_all();
});