您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a few quality of life improvements on Sankaku Channel: 'Choose/Set Parent' modes, automatic image scaling, duplicate tagging/flagging, muting videos, speaker icons on loud videos, + - tag search buttons. Fully configurable using localStorage.
当前为
// ==UserScript== // @name SankakuAddon // @namespace SankakuAddon // @description Adds a few quality of life improvements on Sankaku Channel: 'Choose/Set Parent' modes, automatic image scaling, duplicate tagging/flagging, muting videos, speaker icons on loud videos, + - tag search buttons. Fully configurable using localStorage. // @include http://chan.sankakucomplex.com/* // @include https://chan.sankakucomplex.com/* // @include http://idol.sankakucomplex.com/* // @include https://idol.sankakucomplex.com/* // @run-at document-start // @version 0.98.74 // @grant none // ==/UserScript== "use strict"; var version = "v0.98.74"; if (!String.prototype.startsWith) { String.prototype.startsWith = function(searchString, position) { return this.substr(position || 0, searchString.length) === searchString; }; } /***************************/ /* 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, video_pause:false, video_mute:true, video_controls:true, show_speaker_icon:true, setparent_deletepotentialduplicate:false, hide_headerlogo:false, tag_search_buttons:true, common_tags:"", //deprecated 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\"]} ]" //experimental }; var config_value_types = ["scale_mode", "common_tags", "common_tags_json"]; var use_local_storage = (typeof(Storage) !== "undefined"); var key_prefix = "config."; var config = JSON.parse(JSON.stringify(default_config)); var dom_content_loaded = false; function save_config(key, value) { if (!use_local_storage) { notice("addon: couldn't save setting \"" + key + " = " + value + "\" to local storage. check permissions."); return; } var cfg_key = key_prefix + key; localStorage.setItem(cfg_key, JSON.stringify(value)); } 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(); } 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]); } } } //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 (config_value_types.indexOf(key) != -1) { 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 (config_value_types.indexOf(key) != -1) { 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 ? "" : "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; } //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"; cfg_dialog.style.border = "1px solid #DDD"; cfg_dialog.style.top = "50%"; cfg_dialog.style.transform = "translateY(-50%)"; cfg_dialog.style.width = "450px"; cfg_dialog.style.height = "400px"; 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 = "white"; cfg_dialog.style.zIndex = 10002; cfg_dialog.innerHTML = "" + "<div style='display: table; position: absolute; height: 100%; width: 100%'>" + "<div style='display: table-cell; vertical-align: middle'>" + "<div style='padding: 8px; margin-left: auto; margin-right: auto;'>" + "<b>Sankaku Addon "+version+"</b><br>" + "<hr style='margin-top: 0; margin-bottom: 2px; border:1px solid #DDD;'>" + "<span>" + "<input id='" + key_prefix + "scroll_to_image' type='checkbox'>" + "<span>Scroll to image/video when opening post</span><br>" + "</span>" + "<span>" + "<input id='" + key_prefix + "scale_image' type='checkbox'>" + "<span>Scale image/video when opening post</span><br>" + "</span>" + "<span>" + "<input id='" + key_prefix + "scale_only_downscale' type='checkbox'>" + "<span>Only downscale</span><br>" + "</span>" + "<span>" + "<input id='" + key_prefix + "scale_flash' type='checkbox'>" + "<span>Also scale flash videos</span><br>" + "</span>" + "<span>" + "<input id='" + key_prefix + "scale_on_resize' type='checkbox'>" + "<span title=\"This uses the 'scale image mode' setting, so it doesn't work well when using the manual scaling actions.\" style='cursor:help'>Scale image on window resize</span><br>" + "</span>" + "<span>" + "<span>Scale image/video mode: </span>" + "<select id='" + key_prefix + "scale_mode'>" + "<option value=0>Fit to window</option>" + "<option value=1>Fit horizontally</option>" + "<option value=2>Fit vertically</option>" + "</select>" + "<br>" + "</span>" + "<span>" + "<input id='" + key_prefix + "video_pause' type='checkbox'>" + "<span>Pause (non-flash) videos*</span><br>" + "</span>" + "<span>" + "<input id='" + key_prefix + "video_mute' type='checkbox'>" + "<span>Mute (non-flash) videos*</span><br>" + "</span>" + "<span>" + "<input id='" + key_prefix + "video_controls' type='checkbox'>" + "<span>Show video controls*</span><br>" + "</span>" + "<span>" + "<input id='" + key_prefix + "tag_search_buttons' type='checkbox'>" + "<span>Show + - tag search buttons*</span><br>" + "</span>" + "<span>" + "<input id='" + key_prefix + "show_speaker_icon' type='checkbox'>" + "<span>Show speaker icon on thumbnail if it has audio*</span><br>" + "</span>" + "<span>" + "<input id='" + key_prefix + "setparent_deletepotentialduplicate' type='checkbox'>" + "<span>Delete potential_duplicate tag when using \"Set Parent\"</span><br>" + "</span>" + "<span>" + "<input id='" + key_prefix + "hide_headerlogo' type='checkbox'>" + "<span>Hide header logo</span><br>" + "</span>" + "<span>" + "<input id='" + key_prefix + "common_tags'>" + "<span> Dysfunctional, use field below</span><br>" + "</span>" + "<span>" + "<input id='" + key_prefix + "common_tags_json'>" + "<span> Experimental common tags list (JSON format)*</span><br>" + "</span>" + "<button id='config_close'>Close</button>" + "<button id='config_reset'>Reset settings</button>" + "<span> *requires a page reload.</span>" + "</div>" + ""; 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) { 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 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(" "); } //focus but don't scroll var x = window.scrollX, y = window.scrollY; search_field.focus(); window.scrollTo(x, y); 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); } } /***********************************************/ /* main page / visually similar page functions */ /***********************************************/ var added_mode_options = false; function add_mode_options(mode_dropdown) { if (added_mode_options) return; added_mode_options = true; 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); } } 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; if (img.title.trim().split(/\s+/).indexOf("has_audio") == -1) return; if (a[0].getElementsByClassName("speaker_icon").length > 0) return; a[0].style.display = "inline-block"; //makes the element fit its content a[0].style.position = "relative"; var speaker_icon = document.createElement("SPAN"); speaker_icon.className = "speaker_icon"; speaker_icon.style.color = "#666"; speaker_icon.style.position = "absolute"; speaker_icon.style.top = "2px"; //account for border speaker_icon.style.right = "2px"; speaker_icon.style.fontSize = "200%"; speaker_icon.style.textShadow = "-1px 0 white, 0 1px white, 1px 0 white, 0 -1px white"; speaker_icon.style.transform = "translateX(50%) translateY(-50%)"; speaker_icon.innerHTML = "?"; a[0].appendChild(speaker_icon); } /***********************/ /* post page functions */ /***********************/ //original post/parent ids var post_id = null; var parent_id = null; //original image size var img_elem = null; //image or video var non_img_elem = null, emb_elem = null; //flash or unknown var img_is_flash; var img_width = null; var img_height = null; var img_aspect_ratio = null; var img_current_height = null; //store current height separately, because flash is a bitch var resize_timer; var tag_update_timer; //set by find_actions_list() var actions_ul = null; var found_delete_action = 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.border = "1px solid #DDD"; tag_menu.style.width = "100%"; tag_menu.style.height = "30%"; //TODO variable height tag_menu.style.position = "fixed"; tag_menu.style.bottom = "0"; tag_menu.style.overflow = "auto"; tag_menu.style.backgroundColor = "white"; 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 = "<div style='width: 100%; height: 100%; overflow: auto;'>" + "<span id='common_tags'></span>" + "<br>current tags:<br>" + "<span id='current_tags'></span></div>"; var generate_tag_menu_button = function(id, text) { var button = document.createElement("DIV"); button.id = id; button.style.border = "1px solid #DDD"; button.style.width = "24px"; button.style.height = "24px"; button.style.position = "absolute"; button.style.backgroundColor = "#EEE"; button.innerHTML = "<span style='position: absolute; top: 50%; left: 50%; transform: translateX(-50%) translateY(-50%);'>" + text + "</span>"; button.style.zIndex = 10001; return button; }; var tag_menu_close = generate_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 = generate_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); } 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"); if (common_tags_elem === null || current_tags_elem === null) return; var generate_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.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 generate_li = function(padding, margin) { var li = document.createElement("LI"); li.style.paddingTop = padding; li.style.paddingBottom = padding; li.style.marginLeft = margin; li.style.marginRight = margin; li.style.float = "left"; return li; } var generate_tag_button_li = function(tag) { var li = generate_li("3px", "3px"); li.appendChild(generate_tag_button(tag)); return li; }; var create_tag_list = function() { var ul = document.createElement("UL"); ul.style.listStyleType = "none"; ul.style.margin = "0"; ul.style.padding = "0"; ul.style.display = "inline-block"; return ul; }; //generate tag button list for current tags var current_tags_ul = create_tag_list(); var current_tags = get_tags_array(); for (var i = 0; i < current_tags.length; i++) { current_tags_ul.appendChild(generate_tag_button_li(current_tags[i])); } //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_ul); //now add common tags //common_tags_json should be an array of objects with a string "name" field and an array "tags" field, //where the "tags" array can contain strings (space separated tags) or arrays containing one string (representing a group) //ex. [ { "name":"common tags", "tags":[ "tag1 tag2", ["grouped_tag1 grouped_tag2"] , "tag3 tag4"] }, { "name":"uncommon tags", "tags":[ "t1 t2 t3" ]} ] var tag_data = JSON.parse(config.common_tags_json); if (!Array.isArray(tag_data)) { 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_ul = create_tag_list(); var list_name = tag_data[k].name; var list_tags = tag_data[k].tags; if (!Array.isArray(list_tags)) { notice("addon error: a \"common tags\" object needs to have a \"tags\" array"); return; } for (var i = 0; i < list_tags.length; i++) { var is_group = Array.isArray(list_tags[i]); var tags = (is_group ? list_tags[i][0] : list_tags[i]).trim().split(/\s+/); if (is_group) { //<ul><li><div><li (button)> var div = document.createElement("DIV"); div.style.display = "inline-block"; div.style.marginLeft = "3px"; div.style.marginRight = "3px"; div.style.backgroundColor = "#EEE"; for (var j = 0; j < tags.length; j++) { div.appendChild(generate_tag_button_li(tags[j])); } var li = generate_li("0", "0"); li.appendChild(div); list_ul.appendChild(li); } else { //<ul><li (button)> for (var j = 0; j < tags.length; j++) { list_ul.appendChild(generate_tag_button_li(tags[j])); } } } var span = document.createElement("SPAN"); span.innerHTML = (k != 0 ? "<br>" : "") + (list_name ? list_name + ":<br>" : ""); common_tags_elem.appendChild(span); common_tags_elem.appendChild(list_ul); } } function show_tag_menu(bool) { document.getElementById("tag_menu").style.display = (bool ? "" : "none"); document.getElementById("tag_menu_open").style.display = (!bool ? "" : "none"); } 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) { notice("addon error: couldn't find actions list! Addon actions disabled."); 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(); }, "Fit image", "scale-image-fit"); add_action(function() { scale_image( 1, true); scroll_to_image(); }, "Fit image (Horizontal)", "scale-image-hor"); add_action(function() { scale_image( 2, true); scroll_to_image(); }, "Fit image (Vertical)", "scale-image-ver"); add_action(function() { scale_image(-1, true); scroll_to_image(); }, "Reset image size", "reset-image"); if (parent_id === null) return; //not logged in if (post_id === null) { notice("addon error: couldn't find \"hidden_post_id\" element! 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() { document.getElementById("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)) { notice("addon: cannot tag as duplicate and legitimate_variation at the same time."); return; } if (tags.indexOf(tag) != -1) { 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 (parent_id === null) { notice("addon: user not logged in"); return false; } var current_parent_id = document.getElementById("post_parent_id").value; if (current_parent_id != parent_id) { notice("addon: parent id was changed but not saved!"); return false; } if (!current_parent_id || current_parent_id.length === 0) { 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) { notice("addon: duplicate tag set but not saved!"); return false; } if (old_tags.indexOf("duplicate") == -1) { notice("addon: not tagged as duplicate!"); return false; } if (old_tags.indexOf("legitimate_variation") != -1) { 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; } new Ajax.Request("/post/flag.json", { parameters: { "id": id, "reason": reason }, onComplete: function(response) { var resp = response.responseJSON; if (resp.success) { notice("Post was resent to moderation queue"); } else { notice("Error: " + resp.reason); } } }); } function read_image_data() { //TODO should probably be OOP var img = document.getElementById("image"); //image or video if (img !== null) { img_elem = img; img_is_flash = false; } else { non_img_elem = document.getElementById("non-image-content"); img_elem = non_img_elem.getElementsByTagName("OBJECT")[0]; emb_elem = non_img_elem.getElementsByTagName("EMBED")[0]; img_is_flash = (img_elem !== null && emb_elem !== null); img = img_elem; //object contains width/height } //save original image size if (img_width === null) img_width = img.width; if (img_height === null) img_height = img.height; if (img_aspect_ratio === null) img_aspect_ratio = img.width / img.height; img_current_height = img_height; } //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; try { mode = Number(mode); if (isNaN(mode)) { notice("addon error: scaling mode wasn't a number?"); return; } if (!always_scale && (!config.scale_flash && img_is_flash)) return; //reset image size if (mode === -1) { if (!img_is_flash) { img_elem.style.width = null; img_elem.style.height = null; } else { img_elem.width = img_width; img_elem.height = img_height; emb_elem.width = img_width; emb_elem.height = img_height; } img_current_height = img_height; return; } //target rect img_rect_w = Math.max(window.innerWidth - img_elem.getBoundingClientRect().left - 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 = (img_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 / img_aspect_ratio); //vertical } else if (mode === 2) { new_width = Math.floor(img_rect_h * img_aspect_ratio); new_height = Math.floor(img_rect_h); } if (!always_scale && (config.scale_only_downscale && (new_width > img_width || new_height > img_height))) return; var set_dimensions = function(obj, new_width, new_height) { obj.width = new_width + "px"; obj.height = new_height + "px"; }; if (img_is_flash) { set_dimensions(img_elem, new_width, new_height); set_dimensions(emb_elem, new_width, new_height); } else { set_dimensions(img_elem.style, new_width, new_height); } img_current_height = new_height; } catch(error) { alert("addon error: scaling failed! please report this error to the author: img rect (" + img_width + ", " + img_height + ") to rect (" + img_rect_w + ", " + img_rect_h + "), new size (" + new_width + ", " + new_height + "), exception " + error); } } function scroll_to_image() { var absolute_img_top = (img_is_flash ? non_img_elem : img_elem).getBoundingClientRect().top + window.pageYOffset; var top_of_centered_rect = absolute_img_top - (window.innerHeight - img_current_height) / 2; window.scrollTo(0, top_of_centered_rect); } /******************/ /* document-start */ /******************/ load_config(); /*************************************/ /* main page / visually similar page */ /*************************************/ if (location.pathname === "/" || location.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 speaker icons for dynamically loaded posts (from auto paging) if (config.show_speaker_icon) { //don't hog CPU when disabled, but requires page reload to activate register_observer(function (node) { return (node.classList !== null && node.classList.contains("content-page")); }, function (node, observer) { add_speaker_icons(node); return false; //listen forever }); } /*************/ /* post page */ /*************/ } else if (location.pathname.startsWith("/post/show/")) { var mute_video = function(node) { if (node.nodeType !== Node.ELEMENT_NODE) return; if (node.tagName !== "VIDEO") return; if (config.video_pause) node.pause(); if (config.video_mute) node.muted = true; if (config.video_controls) node.controls = true; }; register_observer(function(node) {return node.id === "image"; }, function(node, observer) { mute_video(node); return true; }); //add speaker icons for dynamically loaded recommendations if (config.show_speaker_icon) { //don't hog CPU when disabled, but requires page reload to activate //this might observe recommendations too early, so add missing speaker icons in DOMContentLoaded register_observer(function (node) { return (node.id === "recommendations"); }, function (node, observer) { add_speaker_icons(node); return false; //listen forever }); } } /******************/ /* content-loaded */ /******************/ document.addEventListener("DOMContentLoaded", function() { 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: #D00; }"; //custom style 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); /*************************************/ /* main page / visually similar page */ /*************************************/ if (location.pathname === "/" || location.pathname.startsWith("/post/similar")) { if (config.show_speaker_icon) { var postlist = document.getElementById("post-list"); if (postlist === null) { notice("addon error: post list not found! Disabled speaker icons."); } else { var elems = postlist.getElementsByTagName("SPAN"); for (var i = 0; i < elems.length; i++) { if (elems[i].classList.contains("thumb")) { add_speaker_icon(elems[i]); } } } } if (config.tag_search_buttons) add_tag_search_buttons(); if (!PostModeMenu.old_change) PostModeMenu.old_change = PostModeMenu.change; if (!PostModeMenu.old_click) PostModeMenu.old_click = PostModeMenu.click; //add change events PostModeMenu.change = function() { var s = $F("mode"); PostModeMenu.old_change(); if (s == "apply-tag-script") { document.body.setStyle({ backgroundColor: "#FDF" //weaken color intensity }); } else if (s == "choose-parent") { document.body.setStyle({ backgroundColor: "#FFD" }); } else if (s == "set-parent") { if (Cookie.get("chosen-parent") === null) { notice("addon: Choose parent first!"); $("mode").value = "choose-parent"; PostModeMenu.change(); } else { document.body.setStyle({ backgroundColor: "#DFF" }); } } }; //add click events PostModeMenu.click = function(post_id) { if (PostModeMenu.old_click(post_id)) { return true; } var s = $("mode"); if (s.value == "choose-parent") { Cookie.put("chosen-parent", post_id); $("mode").value = "set-parent"; PostModeMenu.change(); } else if (s.value == "set-parent") { var parent_id = Cookie.get("chosen-parent"); TagScript.run(post_id, "parent:" + parent_id + (config.setparent_deletepotentialduplicate ? " -potential_duplicate" : "")); } return false; }; PostModeMenu.change(); //guarantee that 'mode' variable correctly changes to new modes when loading page document.onkeydown = function(e) { var tag = e.target.tagName.toLowerCase(); if (tag == "input" || tag == "textarea" || tag == "select") return; var mode = document.getElementById("mode"); if (mode === null) return; if (e.ctrlKey || e.altKey || e.shiftKey) return; switch (e.keyCode) { case 86: //v mode.value = "set-parent"; break; case 67: //c mode.value = "choose-parent"; break; case 81: //q mode.value = "rating-q"; break; case 83: //s mode.value = "rating-s"; break; case 69: //e mode.value = "rating-e"; break; } PostModeMenu.change(); }; /*************/ /* post page */ /*************/ } else if (location.pathname.startsWith("/post/show/")) { var hidden_post_id = document.getElementById("hidden_post_id"); if (hidden_post_id !== null) { post_id = hidden_post_id.innerHTML; } var 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(); add_tag_menu(); updated_tags(); if (config.tag_search_buttons) add_tag_search_buttons(); if (config.show_speaker_icon) add_speaker_icons(document.getElementById("recommendations")); //add what was missing from document-start read_image_data(); if (config.scale_image) scale_image(config.scale_mode, false); if (config.scroll_to_image) scroll_to_image(); document.onkeydown = 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.keyCode) { case 82: //r(eset) scale_image(-1, true); scroll_to_image(); break; case 70: //f(it) scale_image(0, true); scroll_to_image(); break; case 83: //s(imilar) if (post_id === null) { notice("addon error: couldn't find \"hidden_post_id\" element!") } else { window.open("/post/similar?id=" + post_id); } break; case 68: //d(elete) if (post_id === null) { notice("addon error: couldn't find \"hidden_post_id\" element!") } else if (!found_delete_action) { notice("addon error: Delete action not found, no permission?"); } else { window.open("/post/delete/" + post_id); } break; } }; //TODO only add listener when setting is enabled window.addEventListener("resize", function() { clearTimeout(resize_timer); resize_timer = setTimeout(function() { if (config.scale_on_resize) scale_image(config.scale_mode, false); }, 100); }); 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); }); } /*************/ /* pool page */ /*************/ } else if (location.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>"; } } }, false);