您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add autocomplete to the Soybooru upload form
- // ==UserScript==
- // @name Soybooru Ext
- // @namespace https://github.com/thoughever
- // @match https://booru.soy/*
- // @match http://booru.soy/*
- // @match https://www.booru.soy/*
- // @match http://www.booru.soy/*
- // @grant GM_setClipboard
- // @version 0.1.2
- // @author thoughever
- // @license MIT
- // @description Add autocomplete to the Soybooru upload form
- // ==/UserScript==
- (function () {
- "use strict";
- /* Global settings */
- const g_autocomplete_query_delay_ms = 300;
- /* Utils */
- function add_page_styles(styles) {
- let tag_styles = document.createElement('style');
- tag_styles.type = 'text/css';
- tag_styles.innerHTML = styles;
- let head = document.getElementsByTagName('head')[0];
- head.appendChild(tag_styles);
- }
- function insert_after(reference_elem, new_elem) {
- reference_elem.parentNode.insertBefore(new_elem, reference_elem.nextSibling);
- }
- function reverse_str(str){
- return [...str].reverse().join("");
- }
- function insert_mid_str(str, i, j, insert_str) {
- return `${str.slice(0, i)}${insert_str}${str.slice(j, str.length)}`;
- }
- function legacy_copy_to_clipboard(text) {
- let textarea = document.createElement("textarea");
- textarea.classList.add("shimmieext-legacy-clipboard-textarea");
- textarea.value = text;
- document.body.appendChild(textarea);
- textarea.focus();
- textarea.select();
- try {
- document.execCommand("copy");
- } catch (e) {}
- document.body.removeChild(textarea);
- }
- function copy_to_clipboard(text) {
- try {
- GM_setClipboard(text);
- } catch(e) {
- if(navigator.clipboard) {
- navigator.clipboard.writeText(text);
- } else {
- legacy_copy_to_clipboard(text);
- }
- }
- }
- const KEYCODE_TAB = 9;
- const KEYCODE_ENTER = 13;
- const KEYCODE_ARROW_LEFT = 37;
- const KEYCODE_ARROW_UP = 38;
- const KEYCODE_ARROW_RIGHT = 39;
- const KEYCODE_ARROW_DOWN = 40;
- /* Autocomplete elements */
- class AutocompleteDropdown {
- constructor() {
- this.node = document.createElement("ul");
- this.node.classList.add("shimmieext-autocomplete");
- this.node.tabIndex = "-1";
- let _this = this;
- this.node.addEventListener("keydown", function(e) {
- let key_code = e.keyCode || e.which;
- switch(key_code) {
- case KEYCODE_ARROW_UP:
- _this.focus_prev();
- e.preventDefault();
- break;
- case KEYCODE_ARROW_DOWN:
- _this.focus_next();
- e.preventDefault();
- break;
- case KEYCODE_ARROW_RIGHT:
- case KEYCODE_TAB:
- _this.select_focused();
- e.preventDefault();
- }
- }, false);
- this.items = [];
- }
- focus_prev() {
- let prev_li = document.activeElement.previousSibling;
- if(prev_li) {
- prev_li.focus();
- }
- }
- focus_next() {
- let next_li = document.activeElement.nextSibling;
- if(next_li) {
- next_li.focus();
- }
- }
- set_focus(i) {
- let li = this.items[i];
- if(li) {
- li.focus();
- }
- }
- select_focused() {
- let focused_elem = document.activeElement;
- if(this.items.includes(focused_elem)) {
- focused_elem.click();
- }
- }
- select(i) {
- let li = this.items[i];
- if(li) {
- li.click();
- }
- }
- clear() {
- this.node.replaceChildren();
- this.items = [];
- }
- show() {
- this.node.classList.remove("shimmieext-hidden");
- }
- hide() {
- this.node.classList.add("shimmieext-hidden");
- }
- _add_li(li) {
- this.node.appendChild(li);
- this.items.push(li);
- }
- add_item(tag, count, on_select) {
- let new_li = document.createElement("li");
- new_li.classList.add("shimmieext-autocomplete");
- new_li.innerHTML = `${tag} (${count})`;
- new_li.tabIndex = "0";
- let _this = this;
- let hide_select = function() {
- _this.hide();
- on_select(tag);
- };
- new_li.addEventListener("click", function() {
- hide_select();
- }, false);
- new_li.addEventListener("keydown", function(e) {
- let key_code = e.keyCode || e.which;
- if(key_code === KEYCODE_ENTER) {
- hide_select();
- e.preventDefault();
- }
- }, false);
- this._add_li(new_li);
- }
- }
- class AutocompleteField {
- constructor(elem_input, delay_query, shimmie_api) {
- this.root = elem_input;
- this.delay = delay_query;
- this.api = shimmie_api;
- this.timer = undefined;
- const _this = this;
- this.root.addEventListener("input", function(e) {
- let text = _this.root.value;
- if(text) {
- // Reset delay if another key pressed before finished
- _this.clear_timer();
- _this.timer = setTimeout(function() {
- let cursor_i = e.target.selectionStart;
- // If at end of input or space in front
- let cursor_at_end = text[cursor_i] === undefined;
- if(cursor_at_end || text[cursor_i] === " ") {
- // Get word behind cursor
- let i = cursor_i - 1;
- let c = text[i];
- let word_behind = "";
- while(c !== undefined && c !== " ") {
- word_behind += c;
- i--;
- c = text[i];
- }
- let word_start_i = i + 1;
- if(word_behind) {
- word_behind = reverse_str(word_behind);
- _this.api_get_autocomplete(word_behind, function(res_text) {
- _this.populate_dropdown(JSON.parse(res_text), word_start_i, cursor_i, cursor_at_end);
- });
- }
- }
- }, _this.delay);
- }
- }, false);
- this.root.addEventListener("keydown", function(e) {
- let key_code = e.keyCode || e.which;
- switch(key_code) {
- case KEYCODE_TAB:
- case KEYCODE_ARROW_DOWN:
- case KEYCODE_ENTER:
- _this.dropdown.set_focus(0);
- _this.clear_timer();
- e.preventDefault();
- break;
- }
- }, false);
- this.dropdown = new AutocompleteDropdown();
- this.dropdown.hide();
- insert_after(this.root, this.dropdown.node);
- }
- clear_timer() {
- if(this.timer) {
- clearTimeout(this.timer);
- }
- }
- populate_dropdown(res_json, insert_start_i, insert_end_i, add_space) {
- this.dropdown.clear();
- let _this = this;
- for (const [k, v] of Object.entries(res_json)) {
- this.dropdown.add_item(k, v, function(selected_tag) {
- _this.root.value = insert_mid_str(_this.root.value, insert_start_i, insert_end_i, selected_tag);
- if(add_space) {
- _this.root.value += " ";
- }
- _this.root.focus();
- });
- }
- this.dropdown.show();
- }
- api_get_autocomplete(query_text, on_load) {
- const xhttp = new XMLHttpRequest();
- const _this = this;
- xhttp.onload = function() {
- on_load(this.responseText);
- };
- xhttp.open("GET", `${_this.api}/api/internal/autocomplete?s=${query_text}`, true);
- xhttp.send();
- }
- }
- /* Upload form autocomplete */
- function page_upload(shimmie_api) {
- let upload_form = document.getElementById("file_upload");
- // Prevent form submit on enter press
- upload_form.addEventListener("keydown", function(e) {
- let key_code = e.keyCode || e.which;
- if(key_code === KEYCODE_ENTER){
- e.preventDefault();
- }
- }, false);
- // Add enter press submit back to post button
- let upload_button = document.getElementById("uploadbutton");
- upload_button.addEventListener("keydown", function(e) {
- let key_code = e.keyCode || e.which;
- if(key_code === KEYCODE_ENTER){
- upload_form.submit();
- }
- }, false);
- let autocomplete_fields = [];
- let autocomplete_inputs = upload_form.getElementsByClassName('autocomplete_tags');
- for (const input_node of autocomplete_inputs) {
- autocomplete_fields.push(new AutocompleteField(input_node, g_autocomplete_query_delay_ms, shimmie_api));
- }
- // Close dropdown on click anywhere else
- document.body.addEventListener("click", function() {
- for (const field of autocomplete_fields) {
- field.dropdown.hide();
- }
- }, false);
- }
- /* Single page copy tags and autocomplete tag editor */
- function page_single_image(shimmie_api) {
- let tag_editor_input = document.getElementById("tag_editor");
- let tag_editor_autocomplete_field = new AutocompleteField(tag_editor_input, g_autocomplete_query_delay_ms, shimmie_api);
- let tags = tag_editor_input.value;
- let info_table = document.querySelector(".image_info tbody");
- let new_tr = document.createElement("tr");
- let new_td = document.createElement("td");
- let new_button = document.createElement("input");
- new_td.colSpan = "4";
- new_button.value = "Copy Tags";
- new_button.type = "button";
- new_button.classList.add("shimmieext-button-copy-tags");
- new_button.addEventListener("click", function() {
- copy_to_clipboard(tags);
- }, false);
- new_td.appendChild(new_button);
- new_tr.appendChild(new_td);
- info_table.appendChild(new_tr);
- }
- function main() {
- let url = window.location.href.split('/');
- let shimmie_api = `${url[0]}//${url[2]}`;
- let page;
- if(url[3] === "post" && url[4] === "view" || url[3] === "random_image" && url[4] === "view") {
- page = page_single_image;
- } else if(url[3] === "upload") {
- page = page_upload;
- }
- if(page) {
- add_page_styles("ul.shimmieext-autocomplete {list-style: none; padding: 2px; margin: 0; display: block; outline: none; color: #444444; border: 1px solid #dddddd; z-index: 100;\
- font-size: 1.1em; font-family: Helvetica,Arial,sans-serif; text-align: left; background-color: #ffffff; position: absolute; cursor: pointer;}\
- li.shimmieext-autocomplete {list-style: none;}\
- li.shimmieext-autocomplete:hover, li.shimmieext-autocomplete:focus {font-weight: bold; background-color: #0a78eb; color: #ffffff; }\
- .shimmieext-hidden {display: none !important;}\
- .shimmieext-button-copy-tags {cursor: pointer;} .shimmieext-button-copy-tags:active {background-color:#E9E9ED;}\
- .shimmieext-legacy-clipboard-textarea {top: 0; left: 0; position: fixed;}");
- page(shimmie_api);
- }
- }
- main();
- })();