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 = $(""); $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 = $("").html(" → ").addClass("autocomplete-arrow"); var antecedent_element = $("").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 = $("").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 = $("
").append($link); var $list_item = $("
  • ").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;