* Disable autoFocus. This means that the first item in the autocomplete menu isn't automatically selected. * Add Tab keybinding, to make the Tab key work as normal with autoFocus disabled. * Fix the Enter key to 1) insert the selected tag when inside the autocomplete menu, and 2) submit the search as normal when not inside the autocomplete menu.
486 lines
14 KiB
Plaintext
486 lines
14 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") {
|
|
Danbooru.Autocomplete.enable_local_storage = this.test_local_storage();
|
|
this.initialize_tag_autocomplete();
|
|
this.initialize_mention_autocomplete();
|
|
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() {
|
|
var $fields = $(".autocomplete-mentions textarea");
|
|
|
|
$fields.on("keydown.danbooru.autocomplete.tab", null, "tab", Danbooru.Autocomplete.on_tab);
|
|
$fields.autocomplete({
|
|
delay: 500,
|
|
minLength: 2,
|
|
autoFocus: false,
|
|
focus: function() {
|
|
return false;
|
|
},
|
|
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.on("keydown.danbooru.autocomplete.tab", null, "tab", Danbooru.Autocomplete.on_tab);
|
|
$fields_multiple.autocomplete({
|
|
delay: 100,
|
|
autoFocus: false,
|
|
focus: function() {
|
|
return false;
|
|
},
|
|
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 (!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({
|
|
minLength: 1,
|
|
autoFocus: true,
|
|
source: function(req, resp) {
|
|
Danbooru.Autocomplete.normal_source(req.term, resp);
|
|
}
|
|
});
|
|
|
|
$.merge($fields_multiple, $fields_single).each(function(i, field) {
|
|
$(field).data("uiAutocomplete")._renderItem = Danbooru.Autocomplete.render_item;
|
|
});
|
|
}
|
|
|
|
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];
|
|
} else 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-focus").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/>");
|
|
|
|
if (item.antecedent) {
|
|
var antecedent = item.antecedent.replace(/_/g, " ");
|
|
var arrow = $("<span/>").html(" → ").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($link).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 + ":" + 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).success(function(labels) {
|
|
resp(labels.map(function(label) {
|
|
return {
|
|
label: label.replace(/_/g, " "),
|
|
value: "search:" + label,
|
|
};
|
|
}));
|
|
});
|
|
}
|
|
})();
|
|
|
|
$(document).ready(function() {
|
|
Danbooru.Autocomplete.initialize_all();
|
|
});
|