// ==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 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.99.03
// @grant none
// ==/UserScript==
"use strict";
var version = "v0.99.03";
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,
scroll_to_image_center:true,
video_pause:false,
video_mute:true,
set_video_volume:false,
video_volume:100,
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%",
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 config_value_types = ["scale_mode", "common_tags_json", "video_volume", "tag_menu_scale"];
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;
try {
localStorage.setItem(cfg_key, JSON.stringify(value));
} catch (error) {
console.log("SankakuAddon: ", error);
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]);
}
}
}
//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;
}
//"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";
cfg_dialog.style.border = "1px solid " + shifted_backgroundColor(32);
cfg_dialog.style.top = "50%";
cfg_dialog.style.transform = "translateY(-50%)";
cfg_dialog.style.width = "450px";
cfg_dialog.style.height = "470px";
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";
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 " + shifted_backgroundColor(32) + ";'>"
+ "<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 + "scroll_to_image_center' type='checkbox'>"
+ "<span>Scroll to center of image/video, else scroll to top</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 + "set_video_volume' type='checkbox'>"
+ "<span>Set (non-flash) video volume to: </span>"
+ "<input id='" + key_prefix + "video_volume' type='number' min='0' max='100' size='4'>%"
+ "<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 🔊 icon on thumbnail if it has audio*</span><br>"
+ "</span>"
+ "<span>"
+ "<input id='" + key_prefix + "show_animated_icon' type='checkbox'>"
+ "<span>Show ⏩ icon on thumbnail if it is animated (🔊 overrides ⏩)*</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 + "sankaku_channel_dark_compatibility' type='checkbox'>"
+ "<span>Galinoa's Sankaku Channel Dark compatibilty*</span><br>"
+ "</span>"
+ "<span>"
+ "<input id='" + key_prefix + "tag_menu' type='checkbox'>"
+ "<span>Activate tag menu*:</span><br>"
+ "</span>"
+ "<span>"
+ "<input id='" + key_prefix + "common_tags_json'>"
+ "<span> Common tags list (JSON format)*</span><br>"
+ "</span>"
+ "<button id='config_close' style='cursor: pointer'>Close</button>"
+ "<button id='config_reset' style='cursor: pointer'>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);
}
}
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;
if (config.video_controls) node.controls = true;
}
/***********************************************/
/* 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);
}
}
/***********************/
/* post page functions */
/***********************/
var post_parent_id = null; //input elem
//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;
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);
}
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 = "<div style='width: 100%; height: 100%; overflow: auto; margin-left: 2px'>" + "<span id='common_tags'></span>" + "<br>current tags:<br>" + "<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 generate_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 = 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);
var tag_menu_save = generate_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;
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.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 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, padding = "3px", margin = "3px") {
var li = generate_li(padding, margin);
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 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) {
notice("addon error: \"common tags\" JSON syntax error");
return;
}
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;
}
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) {
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 {
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) {
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);
//<ul><li><table><tr><td><li (button)>
var table = document.createElement("TABLE");
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(generate_tag_button_li(tags_table[row][col], "2px", "2px"));
tr.appendChild(td);
}
table.appendChild(tr);
}
var li = generate_li("0", "0");
li.appendChild(table);
list_ul.appendChild(li);
} else if (tags_type === TAGS_TYPES.GROUP) {
if (list_tags[t].length !== 1) {
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+/);
//<ul><li><div><li (button)>
var div = document.createElement("DIV");
div.style.display = "inline-block";
group_style(div);
for (var i = 0; i < tags.length; i++)
div.appendChild(generate_tag_button_li(tags[i], "2px", "2px"));
var li = generate_li("0", "0");
li.appendChild(div);
list_ul.appendChild(li);
} else /* if (tags_type === tag_types.LIST) */ {
//<ul><li (button)>
var tags = list_tags[t].trim().split(/\s+/);
for (var i = 0; i < tags.length; i++)
list_ul.appendChild(generate_tag_button_li(tags[i]));
}
}
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 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) {
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() {
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 = 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;
//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) {
notice("addon error: image/video resolution not in \"Details\"?! Disabled scaling.");
return;
}
if (img_width === null) img_width = res[0];
if (img_height === null) img_height = res[1];
} else {
non_img_elem = document.getElementById("non-image-content");
if (!non_img_elem) return;
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;
}
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 (img_width === null || img_height === null) //read_image_data() failed
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;
//workaround for Galinoa's Sankaku Channel Dark
if (config.sankaku_channel_dark_compatibility) {
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);
img_elem.style.paddingLeft = left_side + "px"; //don't hide behind sidebar
} else {
left_side = 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 = (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 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 (!img_elem) return;
var absolute_img_top = (img_is_flash ? non_img_elem : img_elem).getBoundingClientRect().top + window.pageYOffset;
if (to_center) {
var top_of_centered_rect = absolute_img_top - (window.innerHeight - img_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) {
note_container.style.marginLeft = ((window.innerWidth - img_elem.clientWidth) / 2 - 8) + "px";
}
}
/******************/
/* 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 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 (location.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 (location.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 (location.pathname === "/" || location.pathname.startsWith("/post/similar")) {
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=== "remove-fav") {
document.body.setStyle({
backgroundColor: "#FEA" //slightly more orange
});
} else if (s === "apply-tag-script") {
document.body.setStyle({
backgroundColor: "#FDF" //weaken color intensity
});
} else if (s === "approve") {
document.body.setStyle({
backgroundColor: "#DEF" //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.addEventListener("keydown", 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.key) {
case "v":
mode.value = "set-parent";
break;
case "c":
mode.value = "choose-parent";
break;
case "q":
mode.value = "rating-q";
break;
case "s":
mode.value = "rating-s";
break;
case "e":
mode.value = "rating-e";
break;
}
PostModeMenu.change();
}, true);
/*************/
/* post page */
/*************/
} else if (location.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;
}
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();
if (config.tag_search_buttons) add_tag_search_buttons();
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) {
notice("addon error: couldn't find \"hidden_post_id\" element!")
} else {
window.open("/post/similar?id=" + post_id);
}
break;
case "d": //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;
}
}, true);
if (config.scale_on_resize) add_scale_on_resize_listener();
add_tag_menu_change_listener();
/**************/
/* pool index */
/**************/
} 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>";
}
} else if (location.pathname.startsWith("/wiki/show")) {
var h2 = document.getElementsByClassName("title")[0];
var wiki_edit_link = document.createElement("A");
var tag = new URL(window.location.href).searchParams.get("title");
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);
}