您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a few quality of life improvements on Sankaku Channel: Automatic image scaling, muting videos, speaker icons on loud videos, + - tag search buttons, a tag menu which allows for tagging by clicking, 'Choose/Set Parent' modes, duplicate tagging/flagging. Fully configurable using localStorage.
当前为
// ==UserScript== // @name SankakuAddon // @namespace SankakuAddon // @description Adds a few quality of life improvements on Sankaku Channel: Automatic image scaling, muting videos, speaker icons on loud videos, + - tag search buttons, a tag menu which allows for tagging by clicking, 'Choose/Set Parent' modes, duplicate tagging/flagging. Fully configurable using localStorage. // @include https://chan.sankakucomplex.com/* // @include https://idol.sankakucomplex.com/* // @run-at document-start // @version 0.99.12 // @grant GM.openInTab // @grant unsafeWindow // ==/UserScript== (function(unsafeWindow) { "use strict"; var version = "v0.99.12"; /*****************/ /* compatibility */ /*****************/ var is_greasemonkey4 = false; //script breaking changes (see TODO) var is_monkey = false; //Tampermonkey, Violentmonkey, Greasemonkey (all seem to support 'GM.' functions) if (typeof GM !== "undefined" && typeof GM.info === "object") { is_monkey = true; is_greasemonkey4 = (GM.info.scriptHandler === "Greasemonkey"); } function console_log(msg) { var str; try { str = JSON.parse(JSON.stringify(msg)); } catch { str = msg; } if (typeof window.console != "undefined") { window.console.log(str); } } function open_in_tab(url) { if (is_monkey) GM.openInTab(url, false); else window.open(url); //requires popup permission } function show_notice(msg) { if (unsafeWindow.notice) unsafeWindow.notice(msg); else console_log(msg); } /***************************/ /* configuration functions */ /***************************/ var default_config = { scroll_to_image: true, scale_image: true, //and video scale_only_downscale: false, scale_flash: false, scale_mode: 0, scale_on_resize: false, scroll_to_image_center: true, video_pause: false, video_mute: true, set_video_volume: false, video_volume: 50, video_controls: true, show_speaker_icon: true, show_animated_icon: true, setparent_deletepotentialduplicate: false, hide_headerlogo: false, tag_search_buttons: true, tag_menu: true, tag_menu_scale: "30%", tag_menu_layout: 0, common_tags_json: "[ {\"name\":\"test tags\", \"tags\":[\"tag1 tag2\", [\"grouped_tag1 grouped_tag2\"], \"tag3 tag4\"] }, { \"tags\":[ \"next_line tag5 tag6\", [\"grouped_tag3 grouped_tag4\"] , \"tag7 tag8\"] }, {\"name\":\"another category\", \"tags\":[\"t1 t2 t3\"]} ]", sankaku_channel_dark_compatibility: false }; var use_local_storage = (typeof(Storage) !== "undefined"); var key_prefix = "config."; var config = JSON.parse(JSON.stringify(default_config)); //load default var dom_content_loaded = false; function save_config(key, value, warn = true) { if (!use_local_storage) { if (warn) show_notice("addon: couldn't save setting \"" + key + " = " + value + "\" to local storage. check permissions."); return; } var cfg_key = key_prefix + key; try { localStorage.setItem(cfg_key, JSON.stringify(value)); } catch (error) { console_log("SankakuAddon: ", error); show_notice("addon: couldn't save setting \"" + key + " = " + value + "\" to local storage, check the console."); } } function load_config() { for (var key in config) { if (config.hasOwnProperty(key)) { var value = config[key]; //default already loaded if (use_local_storage) { var cfg_key = key_prefix + key; var stored_value = localStorage.getItem(cfg_key); if (stored_value) value = JSON.parse(stored_value); } config_changed(key, value); //fire regardless } } } function local_storage_changed(e) { if (e.key.startsWith(key_prefix)) { var key = e.key.substring(key_prefix.length); var value = JSON.parse(e.newValue); config_changed(key, value); } } //event that is fired whenever a setting changes in the config dialog or the local storage function config_changed(key, value) { config[key] = value; if (!dom_content_loaded) return; //UI hasn't loaded yet update_config_dialog_by_key(key); if (key === "hide_headerlogo") { update_headerlogo(); } else if (key === "scale_on_resize") { if (value) add_scale_on_resize_listener(); else remove_scale_on_resize_listener(); } } function reset_config() { //clear local storage if (use_local_storage) { for (var key in config) { if (config.hasOwnProperty(key)) { var cfg_key = key_prefix + key; localStorage.removeItem(cfg_key); } } } for (var key in config) if (config.hasOwnProperty(key)) config_changed(key, default_config[key]); } //template for the config dialog var config_template = { "scroll_to_image": {type: "checkbox", desc: "Scroll to image/video when opening post"}, "scroll_to_image_center": {type: "checkbox", desc: "Scroll to center of image/video, else scroll to top"}, "scale_image": {type: "checkbox", desc: "Scale image/video when opening post"}, "scale_only_downscale": {type: "checkbox", desc: "Only downscale"}, "scale_flash": {type: "checkbox", desc: "Also scale flash videos"}, "scale_on_resize": {type: "checkbox", desc: "Scale image on window resize", title: "This uses the 'scale image mode' setting, so it doesn't work well when using the manual scaling actions."}, "scale_mode": {type: "select", desc: "Scale image/video mode: ", options: {0: "Fit to window", 1: "Fit horizontally", 2: "Fit vertically"}}, "video_pause": {type: "checkbox", desc: "Pause (non-flash) videos*"}, "video_mute": {type: "checkbox", desc: "Mute (non-flash) videos*"}, "set_video_volume": {type: "checkbox", desc: "Set (non-flash) video volume to: "}, "video_controls": {type: "checkbox", desc: "Show video controls*"}, "tag_search_buttons": {type: "checkbox", desc: "Show + - tag search buttons*"}, "show_speaker_icon": {type: "checkbox", desc: "Show 🔊 icon on thumbnail if it has audio*"}, "show_animated_icon": {type: "checkbox", desc: "Show ⏩ icon on thumbnail if it is animated (🔊 overrides ⏩)*"}, "setparent_deletepotentialduplicate": {type: "checkbox", desc: "Delete potential_duplicate tag when using \"Set Parent\""}, "hide_headerlogo": {type: "checkbox", desc: "Hide header logo"}, "sankaku_channel_dark_compatibility": {type: "checkbox", desc: "Galinoa's Sankaku Channel Dark compatibilty*"}, "tag_menu": {type: "checkbox", desc: "Activate tag menu*:"}, "common_tags_json": {type: "text", desc: " Common tags list (JSON format)*"}, "tag_menu_layout": {type: "select", desc: "Tag menu layout: ", options: {0: "Normal", 1: "Vertically compact"}} }; //whether a config element's value are accessed via '.value' (or otherwise '.checked') function is_value_element(key) { if (key === "video_volume") return true; //"video_volume" is hardcoded in add_config_dialog() if (key === "tag_menu_scale") return true; //doesn't exist as an element, but it would be '.value' type var type = config_template[key]["type"]; return (type === "select" || type === "text"); } //calls f(cfg_elem, key, get_value) for each existing config element function foreach_config_element(f) { for (var key in config) { if (config.hasOwnProperty(key)) { var cfg_key = key_prefix + key; var cfg_elem = document.getElementById(cfg_key); if (cfg_elem === null) continue; (function(cfg_elem) { if (is_value_element(key)) { f(cfg_elem, key, function() { return cfg_elem.value; }); } else { f(cfg_elem, key, function() { return cfg_elem.checked; }); } })(cfg_elem); } } } function update_config_dialog_by_key(key) { var cfg_key = key_prefix + key; var cfg_elem = document.getElementById(cfg_key); if (cfg_elem !== null) { if (is_value_element(key)) { cfg_elem.value = config[key]; } else { cfg_elem.checked = config[key]; } } } function update_config_dialog() { for (var key in config) if (config.hasOwnProperty(key)) update_config_dialog_by_key(key); } function update_headerlogo() { hide_headerlogo(config.hide_headerlogo); } function show_config_dialog(bool) { document.getElementById("cfg_dialog").style.display = (bool ? "table" : "none"); } /********************/ /* helper functions */ /********************/ function get_scrollbar_width() { //from Stack Overflow var outer = document.createElement("DIV"); outer.style.visibility = "hidden"; outer.style.width = "100px"; document.body.appendChild(outer); var widthNoScroll = outer.offsetWidth; outer.style.overflow = "scroll"; //force scrollbars var inner = document.createElement("DIV"); inner.style.width = "100%"; outer.appendChild(inner); var widthWithScroll = inner.offsetWidth; outer.parentNode.removeChild(outer); return widthNoScroll - widthWithScroll; } //"rgb(r,g,b)" -> [int(r), int(g), int(b)] function rgb_to_array(rgb) { var arr = rgb.substring(rgb.indexOf("(") + 1, rgb.lastIndexOf(")")).split(/,\s*/); for (var i = 0; i < arr.length; i++) arr[i] = parseInt(arr[i]); return arr; } function rgb_array_is_dark(rgb_array) { var avg = 0; for (var i = 0; i < rgb_array.length; i++) avg += rgb_array[i]; avg /= rgb_array.length; return avg <= 128; } function rgb_array_shift(rgb, shift) { var shifted = []; for (var i = 0; i < 3; i++) shifted[i] = Math.min(Math.max(rgb[i] + shift, 0), 255); return shifted; } //[r, g, b] -> "rgb(r,g,b)" function rgb_array_to_rgb(rgb) { if (rgb.length === 3) return "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + rgb[3] + ")"; } //inefficient helper function is_darkmode() { var rgb = rgb_to_array(window.getComputedStyle(document.body, null).getPropertyValue("background-color")); return rgb_array_is_dark(rgb); } //helper function to adjust background colors based on light or dark mode function shifted_backgroundColor(shift) { var rgb = rgb_to_array(window.getComputedStyle(document.body, null).getPropertyValue("background-color")); var darkmode = rgb_array_is_dark(rgb); var shifted_rgb = rgb_array_shift(rgb, (darkmode ? 1 : -1) * shift); return rgb_array_to_rgb(shifted_rgb); } //helper function to modify nodes on creation function register_observer(node_predicate, node_modifier) { var observer = new MutationObserver(function (mutations) { for (var i = 0; i < mutations.length; i++) { var added_nodes = mutations[i].addedNodes; for (var j = 0; j < added_nodes.length; j++) { var node = added_nodes[j]; if (node_predicate(node)) { if (node_modifier(node, observer)) { //are we done? observer.disconnect(); return; } } } } }); observer.observe(document, {childList: true, subtree: true}); return observer; } /*********************/ /* general functions */ /*********************/ var header_offset_height = null; //distance needed to scroll if headerlogo is hidden/shown function hide_headerlogo(hide) { var headerlogo = document.getElementById("headerlogo"); if (headerlogo === null) return; var visible = (headerlogo.style.display !== "none"); headerlogo.style.display = (hide ? "none" : ""); //scroll if needed if (visible === !!hide) window.scrollBy(0, (visible && hide ? -1 : 1) * header_offset_height); } function add_config_dialog() { var cfg_dialog = document.createElement("DIV"); cfg_dialog.id = "cfg_dialog"; cfg_dialog.style.display = "none"; //show_config_dialog() switches this with "table" so that centeringDiv works cfg_dialog.style.border = "1px solid " + shifted_backgroundColor(32); cfg_dialog.style.top = "50%"; cfg_dialog.style.transform = "translateY(-50%)"; cfg_dialog.style.position = "fixed"; cfg_dialog.style.left = "0"; cfg_dialog.style.right = "0"; cfg_dialog.style.margin = "0 auto"; cfg_dialog.style.overflow = "auto"; cfg_dialog.style.backgroundColor = window.getComputedStyle(document.body, null).getPropertyValue("background-color"); cfg_dialog.style.zIndex = "10002"; var centeringDiv = document.createElement("DIV"); centeringDiv.style.display = "table-cell"; centeringDiv.style.verticalAlign = "middle"; cfg_dialog.appendChild(centeringDiv); var innerDiv = document.createElement("DIV"); innerDiv.style.margin = "12px"; innerDiv.id = "cfg_dialog_inner"; centeringDiv.appendChild(innerDiv); //generate the content of the config menu var innerDivHTML = "<div style='font-weight: bold;'>Sankaku Addon " + version + "</div>" + "<hr style='margin-top: 0; margin-bottom: 2px; border:1px solid " + shifted_backgroundColor(32) + ";'>"; //parse the config_template for (var [key, value] of Object.entries(config_template)) { var type = value.type; var generate_span = function (value) { return "<span style='vertical-align: middle;" + (value.title ? "cursor:help; text-decoration: underline dashed;" : "") + "' " + (value.title ? "title='" + value.title + "'" : "") + " >" + value.desc + "</span>"; } innerDivHTML += "<div>" switch (type) { case "checkbox": innerDivHTML += "<input id='" + key_prefix + key + "' type='checkbox' style='vertical-align: middle;'>"; innerDivHTML += generate_span(value); //hardcode 'video_volume' element: innerDivHTML += (key === "set_video_volume" ? "<input id='" + key_prefix + "video_volume' type='number' min='0' max='100' size='4'>%" : ""); break; case "select": innerDivHTML += generate_span(value); innerDivHTML += "<select id='" + key_prefix + key + "'>"; for (var [k, v] of Object.entries(value.options)) innerDivHTML += "<option value=" + k + ">" + v + "</option>"; innerDivHTML += "</select>"; break; case "text": innerDivHTML += "<input id='" + key_prefix + key + "' style='vertical-align: middle;'>"; innerDivHTML += generate_span(value); break; } innerDivHTML += "</div>"; } innerDivHTML += "<div>"; innerDivHTML += "<button id='config_close' style='cursor: pointer;'>Close</button>"; innerDivHTML += "<button id='config_reset' style='cursor: pointer;'>Reset settings</button>"; innerDivHTML += "</div>"; innerDivHTML += "<div> *requires a page reload.</div>"; innerDiv.innerHTML = innerDivHTML; document.body.appendChild(cfg_dialog); //add events document.getElementById("config_close").onclick = function() { show_config_dialog(false); return false; }; document.getElementById("config_reset").onclick = function() { reset_config(); return false; }; foreach_config_element(function(cfg_elem, key, get_value) { cfg_elem.addEventListener("change", function() { config_changed(key, get_value()); save_config(key, get_value()); }); }); } function add_config_button() { var navbar = document.getElementById("navbar"); if (navbar === null) { show_notice("addon error: couldn't find \"navbar\" element! Config dialog disabled."); return; } navbar.style.whiteSpace = "nowrap"; //hack to fit config button var a = document.createElement("A"); a.href = "#"; a.onclick = function() { show_config_dialog(true); return false; }; a.innerHTML = "<span style='font-size: 110%;'>⚙</span> Addon config"; a.style.fontSize = "120%"; var li = document.createElement("LI"); li.className = "lang-select"; //match style of top bar li.appendChild(a); navbar.appendChild(li); } function add_tag_search_buttons() { var tagsidebar = document.getElementById("tag-sidebar"); if (tagsidebar === null) return; var items = tagsidebar.getElementsByTagName("LI"); for (var i = 0; i < items.length; i++) { var taglink = items[i].getElementsByTagName("A")[0]; var tagname = taglink.innerHTML.replace(/ /g, "_"); // " " -> "_" hopefully this is the only edgecase //generates onclick events var tag_search_button_func = function (tagname) { return function () { var search_field = document.getElementById("tags"); var search_tags = search_field.value.trim().split(/\s+/); var tag_index = search_tags.indexOf(tagname); //add tag if missing, remove if existing if (tag_index === -1) { search_field.value = (search_field.value + " " + tagname).trim(); } else { search_tags.splice(tag_index, 1); search_field.value = search_tags.join(" "); } search_field.focus({preventScroll: true}); return false; }; }; var a = document.createElement("A"); a.href = "#"; a.innerHTML = "+"; a.onclick = tag_search_button_func(tagname); items[i].insertBefore(a, taglink); items[i].insertBefore(document.createTextNode(" "), taglink); a = document.createElement("A"); a.href = "#"; a.innerHTML = "-"; a.onclick = tag_search_button_func("-" + tagname); items[i].insertBefore(a, taglink); items[i].insertBefore(document.createTextNode(" "), taglink); } } function add_speaker_icons(root) { if (root === null) return; var elems = root.getElementsByTagName("SPAN"); for (var i = 0; i < elems.length; i++) { if (elems[i].classList.contains("thumb")) { add_speaker_icon(elems[i]); } } } function add_speaker_icon(thumb_span) { var img = thumb_span.querySelector(".preview"); if (img === null) return; var a = thumb_span.getElementsByTagName("A"); if (a.length === 0) return; var icon = document.createElement("SPAN"); var tags = img.title.trim().split(/\s+/); if (config.show_speaker_icon && (tags.indexOf("has_audio") !== -1)) { icon.innerHTML = "🔊"; } else if (config.show_animated_icon && (tags.indexOf("animated") !== -1 || tags.indexOf("video") !== -1)) { icon.innerHTML = "⏩"; } else { return; } icon.className = "speaker_icon"; icon.style.color = "#666"; icon.style.position = "absolute"; icon.style.top = "2px"; //account for border icon.style.right = "2px"; icon.style.fontSize = "200%"; icon.style.textShadow = "-1px 0 white, 0 1px white, 1px 0 white, 0 -1px white"; icon.style.transform = "translateX(50%) translateY(-50%)"; a[0].style.display = "inline-block"; //makes the element fit its content a[0].style.position = "relative"; a[0].appendChild(icon); } function add_speaker_icons_observer(predicate) { if (config.show_speaker_icon || config.show_animated_icon) { //don't hog CPU when disabled, but requires page reload to activate //this might observe recommendations too early, so add missing thumbnail icons in DOMContentLoaded register_observer(predicate, function (node, observer) { add_speaker_icons(node); return false; //listen forever }); } } function configure_video(node) { if (node === null || node.nodeType !== Node.ELEMENT_NODE || node.tagName !== "VIDEO") return; if (config.video_pause) node.pause(); if (config.set_video_volume) node.volume = config.video_volume / 100.0; if (config.video_mute) node.muted = true; node.controls = config.video_controls; } /***********************************************/ /* main page / visually similar page functions */ /***********************************************/ var mode_dropdown = null; var added_mode_options = false; var call_postmodemenu_change = false; function add_mode_options(dropdown) { if (added_mode_options) return; added_mode_options = true; mode_dropdown = dropdown; //override change event mode_dropdown.removeAttribute("onchange"); mode_dropdown.onchange = PostModeMenu_change_override; if (!is_greasemonkey4) { var option; if (mode_dropdown.options.namedItem("choose-parent") === null) { option = document.createElement("option"); option.text = "Choose Parent"; option.value = "choose-parent"; mode_dropdown.add(option); } if (mode_dropdown.options.namedItem("set-parent") === null) { option = document.createElement("option"); option.text = "Set Parent"; option.value = "set-parent"; mode_dropdown.add(option); } } //add_mode_options() was called late if (call_postmodemenu_change) { PostModeMenu_change_override(); //guarantee that 'mode' variable correctly changes to new modes when loading page } } function PostModeMenu_change_override() { if (mode_dropdown === null) return; unsafeWindow.PostModeMenu.change(); var s = mode_dropdown.value; if (s === "remove-fav") { document.body.style.backgroundColor = "#FEA"; //slightly more orange } else if (s === "apply-tag-script") { document.body.style.backgroundColor = "#FDF"; //weaken color intensity } else if (s === "approve") { document.body.style.backgroundColor = "#FDF"; //weaken color intensity } else if (s === "choose-parent") { document.body.style.backgroundColor = "#FFD"; } else if (s === "set-parent") { if (unsafeWindow.Cookie.get("chosen-parent") === "") { show_notice("addon: Choose parent first!"); mode_dropdown.value = "choose-parent"; PostModeMenu_change_override(); } else { document.body.style.backgroundColor = "#DFF"; } } } var PostModeMenu_click_original = null; function PostModeMenu_click_override(post_id) { if (mode_dropdown === null) return; if (PostModeMenu_click_original) if (PostModeMenu_click_original(post_id)) return true; var s = mode_dropdown.value; if (s === "choose-parent") { unsafeWindow.Cookie.put("chosen-parent", post_id); mode_dropdown.value = "set-parent"; PostModeMenu_change_override(); } else if (s === "set-parent") { var parent_id = unsafeWindow.Cookie.get("chosen-parent"); unsafeWindow.TagScript.run(post_id, "parent:" + parent_id + (config.setparent_deletepotentialduplicate ? " -potential_duplicate" : "")); } return false; } /***********************/ /* post page functions */ /***********************/ var post_parent_id = null; //input elem //original post/parent ids var post_id = null; var parent_id = null; var image_data = null; var resize_timer; var tag_update_timer; //set by find_actions_list(): var actions_ul = null; var found_delete_action = false; var mouse_moved = false; //for tag_menu_scaler function tag_menu_scaler_mousedown(e) { e.preventDefault(); mouse_moved = false; window.addEventListener("mousemove", tag_menu_scaler_mousemove); window.addEventListener("mouseup", tag_menu_scaler_mouseup); } function tag_menu_scaler_mousemove(e) { e.preventDefault(); mouse_moved = true; set_tag_menu_scale(e, false); } function tag_menu_scaler_mouseup(e) { e.preventDefault(); if (mouse_moved) set_tag_menu_scale(e, true); window.removeEventListener("mousemove", tag_menu_scaler_mousemove); window.removeEventListener("mouseup", tag_menu_scaler_mouseup); } function set_tag_menu_scale(e, save) { var tag_menu = document.getElementById("tag_menu"); if (tag_menu === null) return; var yFromBottom = window.innerHeight - e.clientY; var yPercentfromBottom = (100.0 * (yFromBottom / window.innerHeight)); yPercentfromBottom = Math.min(Math.max(yPercentfromBottom, 5), 95) + "%"; tag_menu.style.height = yPercentfromBottom; if (save) save_config("tag_menu_scale", yPercentfromBottom, false); } function add_tag_menu() { if (document.getElementById("post_tags") === null) return; //not logged in var tag_menu = document.createElement("DIV"); tag_menu.id = "tag_menu"; tag_menu.style.display = "none"; tag_menu.style.width = "100%"; tag_menu.style.height = config.tag_menu_scale; tag_menu.style.position = "fixed"; tag_menu.style.bottom = "0"; tag_menu.style.overflow = "auto"; tag_menu.style.backgroundColor = window.getComputedStyle(document.body, null).getPropertyValue("background-color"); tag_menu.style.zIndex = "10001"; document.body.appendChild(tag_menu); //the inner div ensures tag_menu_close button doesn't scroll with the content tag_menu.innerHTML = "<span style='width: calc(100% - 2px); height: 100%; overflow: auto;'>" + "<span id='common_tags'></span></span>" + "current tags:" + "<span id='current_tags'></span></div>"; var tag_menu_scaler = document.createElement("DIV"); tag_menu_scaler.id = "tag_menu_scaler"; tag_menu_scaler.style.width = "100%"; tag_menu_scaler.style.height = "6px"; tag_menu_scaler.style.backgroundColor = shifted_backgroundColor(32); tag_menu_scaler.style.position = "absolute"; tag_menu_scaler.style.top = "0"; tag_menu_scaler.style.cursor = "ns-resize"; tag_menu_scaler.style.zIndex = "10000"; tag_menu_scaler.addEventListener("mousedown", tag_menu_scaler_mousedown); tag_menu.appendChild(tag_menu_scaler); tag_menu.style.paddingTop = tag_menu_scaler.style.height; //since tag_menu_scaler floats above the tags var create_tag_menu_button = function(id, text) { var button = document.createElement("DIV"); button.id = id; button.style.border = "1px solid " + shifted_backgroundColor(32); button.style.width = "24px"; button.style.height = "24px"; button.style.position = "absolute"; button.style.textAlign = "center"; button.style.cursor = "pointer"; button.style.backgroundColor = shifted_backgroundColor(16); button.innerHTML = "<span style='width: 100%; display: block; position: absolute; top: 50%; left: 50%; transform: translateX(-50%) translateY(-50%);'>" + text + "</span>"; button.style.zIndex = "10001"; return button; }; var tag_menu_close = create_tag_menu_button("tag_menu_close", "X"); tag_menu_close.style.right = "0"; tag_menu_close.style.top = "0"; tag_menu_close.onclick = function() { show_tag_menu(false); return false; }; tag_menu.appendChild(tag_menu_close); var tag_menu_open = create_tag_menu_button("tag_menu_open", "«"); tag_menu_open.style.position = "fixed"; tag_menu_open.style.right = "0"; tag_menu_open.style.bottom = "0"; tag_menu_open.onclick = function() { show_tag_menu(true); update_tag_menu(); return false; }; document.body.appendChild(tag_menu_open); var tag_menu_save = create_tag_menu_button("tag_menu_save", "Save changes"); tag_menu_save.style.right = "36px"; tag_menu_save.style.width = "140px"; tag_menu_save.style.top = "0"; tag_menu_save.style.fontWeight = "bold"; tag_menu_save.onclick = function() { var form = document.getElementById("edit-form"); if (form !== null) form.submit(); return false; }; tag_menu.appendChild(tag_menu_save); } function update_tag_menu() { if (document.getElementById("post_tags") === null) return; //not logged in var common_tags_elem = document.getElementById("common_tags"); var current_tags_elem = document.getElementById("current_tags"); //tag menu disabled if (common_tags_elem === null || current_tags_elem === null) return; if (Number(config.tag_menu_layout) === 1) { common_tags_elem.style.display = "grid"; common_tags_elem.style.gridTemplateColumns = "fit-content(5%) auto"; } else { common_tags_elem.style.display = ""; } var create_tag_button = function(tag) { var a = document.createElement("A"); a.href = "#"; a.style.paddingLeft = "5px"; a.style.paddingRight = "5px"; a.style.borderStyle = "solid"; a.style.borderWidth = "1px"; a.style.backgroundColor = (is_darkmode() ? "#000" : "#FFF"); //more contrast for tag buttons a.className = (tag_is_present(tag) ? "" : "tag_nonexistent"); a.onclick = function(tag, a) { return function() { if (tag_is_present(tag)) { remove_tag(tag); a.className = "tag_nonexistent"; } else { add_tag(tag); a.className = ""; } return false; }; }(tag, a); a.innerHTML = tag; return a; }; var wrap_in_div = function(el, margin) { var div = document.createElement("DIV"); div.style.margin = margin; div.style.float = "left"; div.appendChild(el); return div; }; var create_top_level_div = function(margin = "3px") { var div = document.createElement("DIV"); div.style.margin = margin; return div; }; var create_tag_list = function() { var div = document.createElement("DIV"); div.style.display = "flex"; div.style.flexWrap = "wrap"; div.style.alignContent = "flex-start"; div.style.alignItems = "flex-start"; div.style.margin = "0"; div.style.padding = "0"; return div; }; //generate tag button list for current tags var current_tags_flex = create_tag_list(); current_tags_flex.style.marginBottom = "3px"; var current_tags = get_tags_array(); for (var i = 0; i < current_tags.length; i++) { var div = create_top_level_div(); div.appendChild(create_tag_button(current_tags[i])); current_tags_flex.appendChild(div); } //replace current list with new one while (current_tags_elem.hasChildNodes()) current_tags_elem.removeChild(current_tags_elem.lastChild); current_tags_elem.appendChild(current_tags_flex); //now add common tags //common_tags_json should be an array of objects with an optional string "name" field and an array "tags" field, //where the "tags" array can contain strings (space separated tags), arrays containing one string (representing a group) //or arrays of array containing one string (representing a table) //ex. [ { "name":"common tags", "tags":[ "tag1 tag2", ["grouped_tag1 grouped_tag2"] , "tag3 tag4"] }, { "name":"uncommon tags", "tags":[ "t1 t2 t3" ]} ] var tag_data; try { tag_data = JSON.parse(config.common_tags_json); } catch (error) { show_notice("addon error: \"common tags\" JSON syntax error"); return; } if (!Array.isArray(tag_data)) { show_notice("addon error: \"common tags\" needs to be an array of objects"); return; } while (common_tags_elem.hasChildNodes()) common_tags_elem.removeChild(common_tags_elem.lastChild); for (var k = 0; k < tag_data.length; k++) { var list_flex = create_tag_list(); var list_name = tag_data[k].name; var list_tags = tag_data[k].tags; if (!Array.isArray(list_tags)) { show_notice("addon error: a \"common tags\" object needs to have a \"tags\" array"); return; } var TAGS_TYPES = { LIST: "list", //e.g. "tag1 tag2" GROUP: "group", //e.g. ["tag1 tag2"] TABLE: "table" //e.g. [["tag1 tag2"], ["tag3 tag4"]] }; var group_style = function(el) { //red in darkmode needs more contrast var rgb = rgb_to_array(window.getComputedStyle(document.body, null).getPropertyValue("background-color")); var darkmode = rgb_array_is_dark(rgb); if (darkmode) { el.style.border = "1px solid " + rgb_array_to_rgb(rgb_array_shift(rgb, 96)); el.style.backgroundColor = rgb_array_to_rgb(rgb_array_shift(rgb, 64)); } else { el.style.border = "1px solid " + rgb_array_to_rgb(rgb_array_shift(rgb, -64)); el.style.backgroundColor = rgb_array_to_rgb(rgb_array_shift(rgb, -32)); } }; for (var t = 0; t < list_tags.length; t++) { var is_array = Array.isArray(list_tags[t]); //find tags_type var tags_type; if (is_array) { if (list_tags[t].length === 0) { show_notice("addon error: \"common tags\" \"tags\" array contains an empty array"); return; } //check what the array consists of var all_arrays = true; var no_arrays = true; for (var i = 0; i < list_tags[t].length; i++) { if (!Array.isArray(list_tags[t][i])) { all_arrays = false; } else { no_arrays = false; } } if (all_arrays) { tags_type = TAGS_TYPES.TABLE; } else if (no_arrays) { tags_type = TAGS_TYPES.GROUP; } else { show_notice("addon error: \"common tags\" \"tags\" array contains an array which is neither a group nor a table"); return; } } else { tags_type = TAGS_TYPES.LIST; } if (tags_type === TAGS_TYPES.TABLE) { var tags_table = []; for (var j = 0; j < list_tags[t].length; j++) { if (list_tags[t][j].length !== 1) { show_notice("addon error: \"common tags\" \"tags\" array contains a table entry with not exactly 1 tags string"); return; } tags_table.push(list_tags[t][j][0].trim().split(/\s+/)); } var table_height = tags_table.length; var table_width = 0; for (var row = 0; row < tags_table.length; row++) table_width = Math.max(table_width, tags_table[row].length); //div (flexbox)><div><table><tr><td><div (button)> var table = document.createElement("TABLE"); table.style.display = "inline-block"; group_style(table); table.style.marginBottom = "0"; for (var row = 0; row < table_height; row++) { var tr = document.createElement("TR"); for (var col = 0; col < table_width; col++) { var td = document.createElement("TD"); td.style.border = "none"; td.style.padding = "0"; if (tags_table[row][col]) td.appendChild(wrap_in_div(create_tag_button(tags_table[row][col]), "1px")); tr.appendChild(td); } table.appendChild(tr); } var div = create_top_level_div("0 3px 0 3px"); div.appendChild(table); list_flex.appendChild(div); } else if (tags_type === TAGS_TYPES.GROUP) { if (list_tags[t].length !== 1) { show_notice("addon error: \"common tags\" \"tags\" array contains a group with not exactly 1 tags string"); return; } var tags = list_tags[t][0].trim().split(/\s+/); //<div (flexbox)><div><div (button)> var group_div = document.createElement("DIV"); group_div.style.display = "inline-block"; group_style(group_div); for (var i = 0; i < tags.length; i++) { group_div.appendChild(wrap_in_div(create_tag_button(tags[i]), "3px")); } var div = create_top_level_div("0 3px 0 3px"); div.appendChild(group_div); list_flex.appendChild(div); } else /* if (tags_type === tag_types.LIST) */ { //<div (flexbox)><div><div (button)> var tags = list_tags[t].trim().split(/\s+/); for (var i = 0; i < tags.length; i++) { var div = create_top_level_div("4px 3px 2px 3px"); div.appendChild(wrap_in_div(create_tag_button(tags[i]))); list_flex.appendChild(div); } } } var span = document.createElement("SPAN"); span.innerHTML = span.innerHTML = (list_name ? list_name + ":" : ""); span.style.paddingTop = "2px"; if (list_name) span.style.marginLeft = "2px"; if (list_name && Number(config.tag_menu_layout) === 1) { var add_top_border = function(el) { el.style.borderTopWidth = "1px"; el.style.borderTopStyle = "solid"; el.style.borderTopColor = shifted_backgroundColor(32); }; add_top_border(span); add_top_border(list_flex); } common_tags_elem.appendChild(span); common_tags_elem.appendChild(list_flex); } } function show_tag_menu(bool) { document.getElementById("tag_menu").style.display = (bool ? "" : "none"); document.getElementById("tag_menu_open").style.display = (!bool ? "" : "none"); } function add_tag_menu_change_listener() { var post_tags_area = document.getElementById("post_tags"); if (post_tags_area !== null) { post_tags_area.addEventListener("change", function() { clearTimeout(tag_update_timer); tag_update_timer = setTimeout(function() { updated_tags(); }, 500); }); } } function find_actions_list() { var li = document.getElementById("add-to-pool"); if (li === null) return; actions_ul = li.parentElement; var action_links = actions_ul.getElementsByTagName("A"); for (var i = 0; i < action_links.length; i++) { if (action_links[i].innerHTML === "Delete") { found_delete_action = true; break; } } } function add_addon_actions() { if (actions_ul === null) return; var separator = document.createElement("H5"); separator.innerHTML = "Addon actions"; var newli = document.createElement("LI"); newli.appendChild(separator); actions_ul.appendChild(newli); var add_action = function(func, name, id) { var a = document.createElement("A"); a.href = "#"; a.onclick = function() {func(); return false;}; a.innerHTML = name; var newli = document.createElement("LI"); newli.id = id; newli.appendChild(a); actions_ul.appendChild(newli); }; add_action(function() { scale_image( 0, true); scroll_to_image(config.scroll_to_image_center); }, "Fit image", "scale-image-fit"); add_action(function() { scale_image( 1, true); scroll_to_image(config.scroll_to_image_center); }, "Fit image (Horizontal)", "scale-image-hor"); add_action(function() { scale_image( 2, true); scroll_to_image(config.scroll_to_image_center); }, "Fit image (Vertical)", "scale-image-ver"); add_action(function() { scale_image(-1, true); scroll_to_image(config.scroll_to_image_center); }, "Reset image size", "reset-image"); if (parent_id === null) return; //not logged in if (post_id === null) { show_notice("addon error: couldn't find post id?! Flag duplicate feature disabled."); return; } add_action(function() { flag_duplicate(post_id, ""); }, "Flag duplicate", "flag-duplicate"); add_action(function() { flag_duplicate(post_id, ", visually identical"); }, "Flag duplicate (identical)", "flag-duplicate-identical"); add_action(function() { flag_duplicate(post_id, " with worse quality"); }, "Flag duplicate (quality)", "flag-duplicate-quality"); add_action(function() { flag_duplicate(post_id, " with worse resolution"); }, "Flag duplicate (resolution)", "flag-duplicate-resolution"); } function add_tag_buttons() { var edit_form = document.getElementById("edit-form"); if (edit_form === null) return; //not logged in var button_place = edit_form.children[1].children[0].children[0].children[0]; button_place.setAttribute("nowrap", "nowrap"); //hack to keep buttons from wrapping (not HTML5 conform, should use CSS) var el = document.createElement("BUTTON"); el.id = "clear_parent_id_button"; el.style.margin = "0 3px 0 6px"; el.innerHTML = "Clear"; el.onclick = function() { post_parent_id.clear(); return false;}; post_parent_id.parentNode.appendChild(el); el = document.createElement("BUTTON"); el.id = "reset_parent_id_button"; el.style.margin = "0 3px"; el.innerHTML = "Reset"; el.onclick = function() { reset_parent_id(); return false;}; post_parent_id.parentNode.appendChild(el); el = document.createElement("BUTTON"); el.id = "tag_reset_button"; el.style.margin = "0 3px 0 6px"; el.innerHTML = "Reset"; el.onclick = function() { reset_tags(); return false; }; button_place.appendChild(el); el = document.createElement("BUTTON"); el.id = "tag_dup_button"; el.style.margin = "0 3px"; button_place.appendChild(el); el = document.createElement("BUTTON"); el.id = "tag_var_button"; el.style.margin = "0 3px"; button_place.appendChild(el); el = document.createElement("BUTTON"); el.id = "tag_pot_button"; el.style.margin = "0 3px"; button_place.appendChild(el); } function update_tag_buttons() { var taglist = document.getElementById("post_tags"); var dup_button = document.getElementById("tag_dup_button"); var var_button = document.getElementById("tag_var_button"); var pot_button = document.getElementById("tag_pot_button"); if (taglist === null || dup_button === null || var_button === null || pot_button === null) return; var tags = get_tags_array(); if (tags.indexOf("duplicate") === -1) { dup_button.onclick = function() {add_tag("duplicate"); return false;}; dup_button.innerHTML = "Tag duplicate"; } else { dup_button.onclick = function() {remove_tag("duplicate"); return false;}; dup_button.innerHTML = "Untag duplicate"; } if (tags.indexOf("legitimate_variation") === -1) { var_button.onclick = function() {add_tag("legitimate_variation"); return false;}; var_button.innerHTML = "Tag legitimate_variation"; } else { var_button.onclick = function() {remove_tag("legitimate_variation"); return false;}; var_button.innerHTML = "Untag legitimate_variation"; } pot_button.innerHTML = "Untag potential_duplicate"; if (tags.indexOf("potential_duplicate") === -1) { pot_button.disabled = true; } else { pot_button.onclick = function() {remove_tag("potential_duplicate"); return false;}; pot_button.disabled = false; } } function reset_parent_id() { post_parent_id.value = parent_id; } function get_old_tags_array() { return document.getElementById("post_old_tags").value.trim().split(/\s+/); } function get_tags_array() { return document.getElementById("post_tags").value.trim().split(/\s+/); } function add_tag(tag) { var tags = get_tags_array(); if ((tag === "duplicate" && tags.indexOf("legitimate_variation") !== -1) || (tag === "legitimate_variation" && tags.indexOf("duplicate") !== -1)) { show_notice("addon: cannot tag as duplicate and legitimate_variation at the same time."); return; } if (tags.indexOf(tag) !== -1) { show_notice("addon: tag already present."); return; } document.getElementById("post_tags").value += " " + tag; updated_tags(); } function remove_tag(tag) { var tags = get_tags_array(); for (var i = 0; i < tags.length; i++) { if (tags[i] === tag) { tags[i] = ""; } } document.getElementById("post_tags").value = tags.join(" ").trim(); updated_tags(); } function tag_is_present(tag) { return get_tags_array().indexOf(tag) !== -1; } function reset_tags() { document.getElementById("post_tags").value = document.getElementById("post_old_tags").value; updated_tags(); } //event that gets called whenever post_tags changes function updated_tags() { update_tag_buttons(); update_tag_menu(); } //flag option with default text function flag_duplicate(id, reason_suffix) { if (is_greasemonkey4) { show_notice("addon error: 'Flag duplicate' not yet supported in Greasemonkey"); return; } if (parent_id === null) { show_notice("addon: user not logged in"); return false; } var current_parent_id = post_parent_id.value; if (current_parent_id !== parent_id) { show_notice("addon: parent id was changed but not saved!"); return false; } if (!current_parent_id || current_parent_id.length === 0) { show_notice("addon: no parent id set!"); return false; } var tags = get_tags_array(); var old_tags = get_old_tags_array(); if (tags.indexOf("duplicate") !== -1 && old_tags.indexOf("duplicate") === -1) { show_notice("addon: duplicate tag set but not saved!"); return false; } if (old_tags.indexOf("duplicate") === -1) { show_notice("addon: not tagged as duplicate!"); return false; } if (old_tags.indexOf("legitimate_variation") !== -1) { show_notice("addon: tagged as legitimate_variation, are you sure it is a duplicate?"); return false; } var reason = prompt("Why should this post be reconsidered for moderation?", "duplicate of " + parent_id + reason_suffix); if (reason === null) { return false; } //TODO will not work on Greasemonkey at all new unsafeWindow.Ajax.Request("/post/flag.json", { parameters: { "id": id, "reason": reason }, onComplete: function(response) { var resp = response.responseJSON; if (resp.success) { show_notice("Post was resent to moderation queue"); } else { show_notice("Error: " + resp.reason); } } }); } function read_image_data() { var data = { img_elem: null, //image or video non_img_elem: null, emb_elem: null, //flash or unknown is_flash: null, width: null, height: null, aspect_ratio: null, current_height: null //store current height separately, because flash is a bitch }; var img = document.getElementById("image"); if (img !== null) { //image or video data.img_elem = img; data.is_flash = false; //workaround for Galinoa's Sankaku Channel Dark: don't read .width/.height attributes but read "Details" var res = null; var lowres = document.getElementById("lowres"); if (lowres !== null) { res = lowres.innerHTML.split("x"); //parse "<width>x<height>" } else { var highres = document.getElementById("highres"); if (highres !== null) { res = highres.innerHTML.split(" ")[0].split("x"); //parse "<width>x<height> (<file size>)" } } if (res === null) { show_notice("addon error: image/video resolution not in \"Details\"?! Disabled scaling."); return null; } data.width = res[0]; data.height = res[1]; } else { //flash or unknown data.non_img_elem = document.getElementById("non-image-content"); if (!data.non_img_elem) return null; data.img_elem = data.non_img_elem.getElementsByTagName("OBJECT")[0]; data.emb_elem = data.non_img_elem.getElementsByTagName("EMBED")[0]; data.is_flash = (data.img_elem !== null && data.emb_elem !== null); img = data.img_elem; //object contains width/height data.width = img.width; data.height = img.height; } data.aspect_ratio = data.width / data.height; data.current_height = data.height; return data; } //stretch image/video/flash, requires data from read_image_data() function scale_image(mode, always_scale) { var img_rect_w, img_rect_h; var new_width, new_height; mode = Number(mode); if (isNaN(mode)) { show_notice("addon error: scaling mode wasn't a number?!"); return; } //read_image_data() failed if (image_data === null) return; if (!always_scale && (!config.scale_flash && image_data.is_flash)) return; //reset image size if (mode === -1) { if (!image_data.is_flash) { image_data.img_elem.style.width = null; image_data.img_elem.style.height = null; } else { image_data.img_elem.width = image_data.width; image_data.img_elem.height = image_data.height; image_data.emb_elem.width = image_data.width; image_data.emb_elem.height = image_data.height; } image_data.current_height = image_data.height; //workaround for Galinoa's Sankaku Channel Dark if (config.sankaku_channel_dark_compatibility) { image_data.img_elem.style.paddingLeft = ""; note_fix(); } return; } var left_side; //workaround for Galinoa's Sankaku Channel Dark //problem: seems to only work for bigger windows if (config.sankaku_channel_dark_compatibility) { var sidebar = document.getElementsByClassName("sidebar")[0]; left_side = (sidebar.getBoundingClientRect().right + 12); image_data.img_elem.style.paddingLeft = left_side + "px"; //don't hide behind sidebar } else { left_side = image_data.img_elem.getBoundingClientRect().left; } //target rect img_rect_w = Math.max(window.innerWidth - left_side - get_scrollbar_width() - 1, 1); img_rect_h = Math.max(window.innerHeight - 1, 1); var img_rect_aspect_ratio = img_rect_w / img_rect_h; //fit into window if (mode === 0) { mode = (image_data.aspect_ratio > img_rect_aspect_ratio ? 1 : 2); } //horizontal if (mode === 1) { new_width = Math.floor(img_rect_w); new_height = Math.floor(img_rect_w / image_data.aspect_ratio); //vertical } else if (mode === 2) { new_width = Math.floor(img_rect_h * image_data.aspect_ratio); new_height = Math.floor(img_rect_h); } if (!always_scale && (config.scale_only_downscale && (new_width > image_data.width || new_height > image_data.height))) return; var set_dimensions = function(obj, new_width, new_height) { obj.width = new_width + "px"; obj.height = new_height + "px"; }; if (image_data.is_flash) { set_dimensions(image_data.img_elem, new_width, new_height); set_dimensions(image_data.emb_elem, new_width, new_height); } else { set_dimensions(image_data.img_elem.style, new_width, new_height); } image_data.current_height = new_height; } function scale_on_resize_helper() { clearTimeout(resize_timer); resize_timer = setTimeout(function() { if (config.scale_on_resize) scale_image(config.scale_mode, false); }, 100); } function add_scale_on_resize_listener() { window.addEventListener("resize", scale_on_resize_helper); } function remove_scale_on_resize_listener() { window.removeEventListener("resize", scale_on_resize_helper); } function scroll_to_image(to_center) { if (image_data === null) return; var absolute_img_top = (image_data.is_flash ? image_data.non_img_elem : image_data.img_elem).getBoundingClientRect().top + window.pageYOffset; if (to_center) { var top_of_centered_rect = absolute_img_top - (window.innerHeight - image_data.current_height) / 2; window.scrollTo(0, top_of_centered_rect); } else { window.scrollTo(0, absolute_img_top); } } //simple note fix for Galinoa's Sankaku Channel Dark (only for default image size) function note_fix() { var note_container = document.getElementById("note-container"); if (note_container !== null && image_data !== null) { note_container.style.marginLeft = ((window.innerWidth - image_data.img_elem.clientWidth) / 2 - 8) + "px"; } } /******************/ /* document-start */ /******************/ load_config(); /*************************************/ /* main page / visually similar page */ /*************************************/ //skip language codes in pathnames like "/jp/post/show" var pathname = location.pathname; if (pathname.indexOf("/", 1) === 3) { pathname = pathname.substring(3); } if (pathname === "/" || pathname.startsWith("/post/similar")) { //try to add new modes right after the "Apply tag script" mode is added to prevent it being reset to "View posts" on page reloads //it's possible we are too late to observe its construction, so look for it afterwards immediately var observer = register_observer(function(node) { return (node.value === "apply-tag-script"); }, function(node, observer) { observer.disconnect(); add_mode_options(node.parentNode); return false; }); var dropdown = document.getElementById("mode"); if (dropdown !== null) { var children = dropdown.childNodes; for (var i = 0; i < children.length; i++) { if (children[i].value === "apply-tag-script") { //it's already there observer.disconnect(); //stop looking for it add_mode_options(dropdown); } } } //add thumbnail icons for dynamically loaded posts (from auto paging) if (config.show_speaker_icon || config.show_animated_icon) { add_speaker_icons_observer(function (node) { return (node.classList != null && node.classList.contains("content-page")); }); } /*************/ /* post page */ /*************/ } else if (pathname.startsWith("/post/show/")) { //mute/pause videos var observer = register_observer(function(node) {return node.id === "image"; }, function(node, observer) { configure_video(node); return true; }); var video = document.getElementById("image"); if (video !== null) { observer.disconnect(); configure_video(video); } add_speaker_icons_observer(function (node) { return (node.id === "recommendations"); }); /*************/ /* user page */ /*************/ } else if (pathname.startsWith("/user/show/")) { add_speaker_icons_observer(function (node) { return (node.id === "recommendations"); }); } /******************/ /* content-loaded */ /******************/ function init() { dom_content_loaded = true; //sitefix for flagged posts not always showing red border //problem: "flagged" style is defined before "has-parent" and "has-children" CSS styles, so these two take priority //fix: just add another copy of the "flagged" style at the end var sheet = document.createElement("style"); sheet.innerHTML = "img.has-children {padding:0px;border:2px solid #A7DF38;} img.has-parent {padding:0px;border:2px solid #CCCC00;} img.flagged {padding: 0px; border: 2px solid #F00;}"; sheet.innerHTML += " a.tag_nonexistent { color: #E00; }"; //custom style for tag menu document.body.appendChild(sheet); var headerlogo = document.getElementById("headerlogo"); if (headerlogo !== null) { header_offset_height = headerlogo.offsetHeight; update_headerlogo(); } add_config_dialog(); add_config_button(); update_config_dialog(); //listen for config changes in other windows window.addEventListener("storage", local_storage_changed); if (config.show_speaker_icon || config.show_animated_icon) add_speaker_icons(document); /*************************************/ /* main page / visually similar page */ /*************************************/ if (pathname === "/" || pathname.startsWith("/post/similar")) { if (config.tag_search_buttons) add_tag_search_buttons(); if (!is_greasemonkey4) { PostModeMenu_click_original = unsafeWindow.PostModeMenu.click; //TODO will not work on Greasemonkey (need to replace click events just like with the mode change event) unsafeWindow.PostModeMenu.click = PostModeMenu_click_override; } if (added_mode_options) { //add_mode_options() was called early, as it should PostModeMenu_change_override(); //guarantee that 'mode' variable correctly changes to new modes when loading page } else { //if not, catch up on it later call_postmodemenu_change = true; } document.addEventListener("keydown", function(e) { var tag = e.target.tagName.toLowerCase(); if (tag === "input" || tag === "textarea" || tag === "select") return; if (mode_dropdown === null) return; if (e.ctrlKey || e.altKey || e.shiftKey) return; switch (e.key) { case "v": if (is_greasemonkey4) { show_notice("addon error: 'Set Parent' not yet supported in Greasemonkey"); return; } mode_dropdown.value = "set-parent"; break; case "c": if (is_greasemonkey4) { show_notice("addon error: 'Choose Parent' not yet supported in Greasemonkey"); return; } mode_dropdown.value = "choose-parent"; break; case "q": mode_dropdown.value = "rating-q"; break; case "s": mode_dropdown.value = "rating-s"; break; case "e": mode_dropdown.value = "rating-e"; break; } PostModeMenu_change_override(); }, true); /*************/ /* post page */ /*************/ } else if (pathname.startsWith("/post/show/")) { var hidden_post_id_el = document.getElementById("hidden_post_id"); if (hidden_post_id_el !== null) { post_id = hidden_post_id_el.innerHTML; } else { post_id = pathname.substring(pathname.lastIndexOf("/") + 1); } post_parent_id = document.getElementById("post_parent_id"); if (post_parent_id !== null) { parent_id = post_parent_id.value; } find_actions_list(); add_addon_actions(); add_tag_buttons(); if (config.tag_menu) add_tag_menu(); updated_tags(); //initialize tag menu if (config.tag_search_buttons) add_tag_search_buttons(); image_data = read_image_data(); if (config.scale_image) scale_image(config.scale_mode, false); if (config.scroll_to_image) scroll_to_image(config.scroll_to_image_center); if (config.sankaku_channel_dark_compatibility) note_fix(); document.addEventListener("keydown", function(e) { var tag = e.target.tagName.toLowerCase(); if (tag === "input" || tag === "textarea" || tag === "select") return; if (e.ctrlKey || e.altKey || e.shiftKey) return; switch (e.key) { case "r": //r(eset) scale_image(-1, true); scroll_to_image(config.scroll_to_image_center); break; case "f": //f(it) scale_image(0, true); scroll_to_image(config.scroll_to_image_center); break; case "g": //g scale_image(1, true); scroll_to_image(config.scroll_to_image_center); break; case "h": //h scale_image(2, true); scroll_to_image(config.scroll_to_image_center); break; case "s": //s(imilar) if (post_id === null) { show_notice("addon error: couldn't find post id?!"); } else { open_in_tab(location.origin + "/post/similar?id=" + post_id); } break; case "d": //d(elete) if (post_id === null) { show_notice("addon error: couldn't find post id?!"); } else if (!found_delete_action) { show_notice("addon error: Delete action not found, no permission?"); } else { open_in_tab(location.origin + "/post/delete/" + post_id); } break; } }, true); if (config.scale_on_resize) add_scale_on_resize_listener(); add_tag_menu_change_listener(); /**************/ /* pool index */ /**************/ } else if (pathname.startsWith("/pool/index")) { //sitefix to show pool links even if missing english translation (they could not be clicked on otherwise) var pool_entries = document.getElementById("pool-index").getElementsByTagName("TABLE")[0].getElementsByTagName("TBODY")[0].getElementsByTagName("TR"); for (var i = 0; i < pool_entries.length; i++) { var pool_name = pool_entries[i].getElementsByTagName("TD")[0].getElementsByTagName("A")[0]; if (pool_name.innerHTML.trim().length === 0) pool_name.innerHTML = "<missing English translation>"; } /*************/ /* wiki page */ /*************/ } else if (pathname.startsWith("/wiki/show")) { //add a "⚙" link to the edit tag page var h2 = document.getElementsByClassName("title")[0]; var tag = new URL(window.location.href).searchParams.get("title"); var wiki_edit_link = document.createElement("A"); wiki_edit_link.href = "/tag/edit?name=" + tag; wiki_edit_link.innerHTML = "⚙"; wiki_edit_link.title = "Edit Tag"; h2.appendChild(wiki_edit_link); } } if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") { init(); } else { document.addEventListener("DOMContentLoaded", init, false); } })(typeof unsafeWindow !== "undefined" ? unsafeWindow : window);