Previously we patched the jqueryui-autocomplete library in order to customize how the Tab and Enter keys behaved. Specifically, we wanted to prevent the Tab key from moving the focus out of the tag input box, and we wanted to prevent the Enter key from submitting the page when editing tags. These things can achieved without patching the library by using `event.preventDefault` and `event.stopImmediatePropagation` to prevent other event handlers from running after these keys trigger the `autocompleteselect` event.
469 lines
13 KiB
Plaintext
469 lines
13 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.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.autocomplete({
|
|
delay: 500,
|
|
minLength: 2,
|
|
autoFocus: true,
|
|
focus: function() {
|
|
return false;
|
|
},
|
|
select: function(event, ui) {
|
|
var before_caret_text = this.value.substring(0, this.selectionStart).replace(/\S+$/, ui.item.value + " ");
|
|
var after_caret_text = this.value.substring(this.selectionStart);
|
|
this.value = before_caret_text;
|
|
|
|
// Preserve original caret position to prevent it from jumping to the end
|
|
var original_start = this.selectionStart;
|
|
this.value += after_caret_text;
|
|
this.selectionStart = this.selectionEnd = original_start;
|
|
|
|
// Prevent the enter key from submitting the tag edit form (Danbooru.Upload.initialize_enter_on_tags).
|
|
event.stopImmediatePropagation();
|
|
// Prevent the tab key from moving the focus to the next element.
|
|
event.preventDefault();
|
|
|
|
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"]');
|
|
|
|
var prefixes = "-|~|" + "<%= TagCategory.mapping.keys.map {|category| category + ':'}.join('|') %>";
|
|
var metatags = "<%= Tag::METATAGS %>";
|
|
|
|
$fields_multiple.autocomplete({
|
|
delay: 100,
|
|
autoFocus: true,
|
|
focus: function() {
|
|
return false;
|
|
},
|
|
select: function(event, ui) {
|
|
var before_caret_text = this.value.substring(0, this.selectionStart);
|
|
var after_caret_text = this.value.substring(this.selectionStart);
|
|
var regexp = new RegExp("(" + prefixes + ")?\\S+$", "g");
|
|
this.value = before_caret_text.replace(regexp, "$1" + ui.item.value + " ");
|
|
|
|
// Preserve original caret position to prevent it from jumping to the end
|
|
var original_start = this.selectionStart;
|
|
this.value += after_caret_text;
|
|
this.selectionStart = this.selectionEnd = original_start;
|
|
|
|
event.stopImmediatePropagation();
|
|
event.preventDefault();
|
|
return false;
|
|
},
|
|
source: function(req, resp) {
|
|
var before_caret_text = req.term.substring(0, this.element.get(0).selectionStart);
|
|
|
|
if (before_caret_text.match(/ $/)) {
|
|
this.close();
|
|
return;
|
|
}
|
|
|
|
var term = before_caret_text.match(/\S+/g);
|
|
if (!term) {
|
|
return;
|
|
}
|
|
|
|
term = term.pop();
|
|
var regexp = new RegExp("^(?:" + prefixes + ")(.*)$", "i");
|
|
var match = term.match(regexp);
|
|
if (match) {
|
|
term = match[1];
|
|
}
|
|
if (term === "") {
|
|
return;
|
|
}
|
|
|
|
regexp = new RegExp("^(" + metatags + "):(.*)$", "i");
|
|
match = term.match(regexp);
|
|
var metatag;
|
|
if (match) {
|
|
metatag = match[1].toLowerCase();
|
|
term = match[2];
|
|
}
|
|
|
|
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 %>
|
|
return;
|
|
|
|
case "order":
|
|
case "status":
|
|
case "rating":
|
|
case "locked":
|
|
case "child":
|
|
case "parent":
|
|
case "filetype":
|
|
Danbooru.Autocomplete.static_metatag_source(term, resp, metatag);
|
|
return;
|
|
}
|
|
|
|
if (term === "") {
|
|
return;
|
|
}
|
|
|
|
switch(metatag) {
|
|
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.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();
|
|
});
|