SankakuAddon

Adds a few quality of life improvements on Sankaku Channel: 'Choose/Set Parent' mode, automatic image scaling, duplicate tagging/flagging, muting videos. Fully configurable using localStorage.

Από την 10/03/2017. Δείτε την τελευταία έκδοση.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name        SankakuAddon
// @namespace   SankakuAddon
// @description Adds a few quality of life improvements on Sankaku Channel: 'Choose/Set Parent' mode, automatic image scaling, duplicate tagging/flagging, muting videos. 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
// @grant       none
// ==/UserScript==

"use strict";
var version = "v0.98";

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:"1girl 2girls 3girls 4girls 5girls 6+girls 1boy 2boys 3boys 4boys 5boys 6+boys" //experimental
};
var config_value_types = ["scale_mode", "common_tags"];


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)) { //are we done?
            observer.disconnect();
            return;
          }
        }
      }
    }
  });
  observer.observe(document, {childList: true, subtree: true});
}


/*********************/
/* 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");
  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 = "300px";
  cfg_dialog.style.width = "400px";
  cfg_dialog.style.height = "370px";
  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> Experimental common tags list*</span><br>"
    +   "</span>"
    +   "<button id='config_close'>Close</button>"
    +   "<button id='config_reset'>Reset settings</button>"
    +   "<span>&nbsp;*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) {
    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, add) {
      return function() {
        var search_field = document.getElementById("tags");
        if (add) {
          search_field.value = (search_field.value + " " + tagname).trim();
        } else {
          search_field.value = search_field.value.replace(new RegExp(tagname, "g"), "").replace(new RegExp("  ", "g"), " ").trim();
        }

        //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, true);

    items[i].insertBefore(a, taglink);

    a = document.createElement("A");
    a.href = "#";
    a.innerHTML = "- ";
    a.onclick = tag_search_button_func(tagname, false);

    items[i].insertBefore(a, taglink);
  }
}


/***********************************************/
/* main page / visually similar page functions */
/***********************************************/

function add_mode_options(mode_dropdown) {
  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);
  }
}

var added_mode_options = function() {
  var i = 0;
  return function() {
    i++;
    if (i == 2) { //wait for mode options to be added and PostModeMenu to change
      PostModeMenu.change(); //guarantee that 'mode' variable correctly changes to new modes when loading page
    }
  };
}();
var updated_PostModeMenu = added_mode_options;

function add_speaker_icon(span) {
  var img = span.querySelector(".preview");
  if (img === null) {
    notice("addon error: preview image not found?");
    return;
  }
  var a = span.getElementsByTagName("A");
  if (a.length == 0) {
    notice("addon error: preview image not clickable?");
    return;
  }

  if (img.title.trim().split(/\s+/).indexOf("has_audio") == -1)
    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.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;

function add_tag_menu() {
  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;'>common tags:<br>" + "<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() {
  var common_tags  = document.getElementById("common_tags");
  var current_tags = document.getElementById("current_tags");

  if (common_tags === null || current_tags === 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_tag_button_li = function(tag) {
    var li = document.createElement("LI");
    li.style.paddingTop    = "3px";
    li.style.paddingBottom = "3px";
    li.style.float = "left";
    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;
  };

  var ul1 = create_tag_list();
  var ul2 = create_tag_list();

  var common_tags_array = config.common_tags.trim().split(/\s+/);
  for (var i = 0; i < common_tags_array.length; i++) {
    ul1.appendChild(generate_tag_button_li(common_tags_array[i]));
  }

  var tags = get_tags_array();
  for (var i = 0; i < tags.length; i++) {
    ul2.appendChild(generate_tag_button_li(tags[i]));
  }

  while (common_tags.hasChildNodes()) {
    common_tags.removeChild(common_tags.lastChild);
  }
  while (current_tags.hasChildNodes()) {
    current_tags.removeChild(current_tags.lastChild);
  }

  common_tags.appendChild(ul1);
  current_tags.appendChild(ul2);
}

function show_tag_menu(bool) {
  document.getElementById("tag_menu").style.display = (bool ? "" : "none");
  document.getElementById("tag_menu_open").style.display = (!bool ? "" : "none");
}

function add_addon_actions() {
  var li = document.getElementById("add-to-pool");
  if (li === null) {
    notice("addon error: couldn't find \"add-to-pool\" element! Addon actions disabled.");
    return;
  }

  var actions_ul = li.parentElement;

  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;

  if (post_id !== null) {
    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) {
    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) {
  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
  var img_rect_w = Math.max(window.innerWidth - img_elem.getBoundingClientRect().left - get_scrollbar_width() - 1, 1);
  var 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);
  }

  var new_width, new_height;

  //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;
}

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")) {

  //add new modes right after dropdown and "apply-tag-script" mode was added to prevent it being reset to "view post" on page reloads
  register_observer(function(node) {
    return (node.value === "apply-tag-script");
  }, function(node) {
    add_mode_options(node.parentNode);
    added_mode_options();
    return true;
  });

  //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) {
      var elems = node.getElementsByTagName("SPAN");
      for (var i = 0; i < elems.length; i++) {
        if (elems[i].classList.contains("thumb")) {
          add_speaker_icon(elems[i]);
        }
      }
      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) { mute_video(node); return true; });

}




/******************/
/* 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);

  header_offset_height = document.getElementById("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 elems = document.getElementById("post-list").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;
    };

    updated_PostModeMenu();


  /*************/
  /* 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;
    }

    add_addon_actions();
    add_tag_buttons();
    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();

    //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.addEventListener) 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 = "&lt;missing English translation&gt;";
    }

  }

}, false);