Marti / uso - Monkey Barrel

(function () {

// ==UserScript==
// @run-at        document-start
// @name          uso - Monkey Barrel
// @namespace     http://userscripts.org/users/37004
// @description   Enhanced menu system for Userscripts.org
// @copyright     2011+, Marti Martz (http://userscripts.org/users/37004)
// @license       CC-BY-NC-SA-4.0; https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
// @license       GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt
// @version       0.1.13.1eol
// @icon          https://raw.githubusercontent.com/Martii/UserScripts/master/src/uso/Monkey%20Barrel/res/icon48.png

// @homepageURL   https://github.com/Martii/UserScripts/tree/master/src/uso/Monkey%20Barrel
// @homepageURL   https://openuserjs.org/scripts/Marti/uso_-_Monkey_Barrel
// @homepageURL   http://userscripts.org/scripts/show/114843
// @supportURL    http://userscripts.org/topics/89959

// @include   /^https?://userscripts\.org(?::\d{1,5})?/?.*/

// @include   http://userscripts.org:8080/*

// @include   http://userscripts.org/*

// @include   https://userscripts.org/*

// @require   https://raw.githubusercontent.com/Martii/UserScripts/master/lib/GM_setStyle/GM_setStyle.js
// @require   https://raw.githubusercontent.com/Martii/GM_config/42d6367b3c8ccc1b8f32af7b23fce5078716ff14/gm_config.js

// @resource  gmc https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/gm_config_icon.png

// @grant GM_addStyle
// @grant GM_getResourceURL
// @grant GM_getValue
// @grant GM_log
// @grant GM_xmlhttpRequest
// @grant GM_setValue

// ==/UserScript==

/*

Please note this script uses native JSON and native classList which requires Firefox 3.6.x+ and Greasemonkey 0.9.8+

*/

  let protocol = "http" + (/^https:$/i.test(location.protocol) ? "s" : "") + ":";

  function findlastPost(aTopicid) {
    if (document && document.body)
      document.body.style.cursor = "progress";

    GM_xmlhttpRequest({
      retry: 5,
      url: "/topics/" + aTopicid + ".rss",
      method: "GET",
      onload: function (xhr) {
        switch (xhr.status) {
          case 502:
          case 503:
            if (--this.retry > 0)
              setTimeout(GM_xmlhttpRequest, 3000 + Math.round(Math.random() * 5000), this);
            else {
              if (document && document.body)
                document.body.style.cursor = "auto";
            }
            break;
          case 200:
            let doc = new DOMParser().parseFromString(xhr.responseText, "text/xml"),
                xpr = doc.evaluate(
                    "//channel/item/link",
                    doc.documentElement,
                    null,
                    XPathResult.FIRST_ORDERED_NODE_TYPE,
                    null
                );

            if (xpr && xpr.singleNodeValue) {
              let thisNode = xpr.singleNodeValue;

              if (/^https?:\/\/userscripts\.org(?::\d{1,5})?\/posts\/\d+\/?/i.test(thisNode.textContent)) {
                let url = thisNode.textContent.replace(/http:/i, location.protocol);
                GM_xmlhttpRequest({
                  retry: 5,
                  url: thisNode.textContent.replace(/http:/i, location.protocol),
                  method: "HEAD",
                  onload: function (xhr) {
                    // NOTE: Secure xhr is prevented currently on USO and GM_xhr currently has an issue with onreadystatechange in FF 7.0.1,
                    //       so rely upon finalUrl in unsecure mode but redirect to secure
                    switch (xhr.status) {
                      case 502:
                      case 503:
                        if (--this.retry > 0)
                          setTimeout(GM_xmlhttpRequest, 3000 + Math.round(Math.random() * 5000), this);
                        else {
                          if (document && document.body)
                            document.body.style.cursor = "auto";
                        }
                        break;
                      case 200:
                        if (document && document.body) {
                          document.body.style.cursor = "auto";
                          location.assign(xhr.finalUrl.replace(/^http:/i, location.protocol));
                        }
                        else {
                          location.replace(xhr.finalUrl.replace(/^http:/i, location.protocol));
                        }
                        break;
                      default:
                        if (document && document.body)
                          document.body.style.cursor = "auto";
                        break;
                    }
                  }
                });
              }
            }
            break;
          default:
            if (document && document.body)
              document.body.style.cursor = "auto";
          break;
        }
      }
    });
  }

  // ** Accelerator function event
  function lastPost(ev) {
    let topicid = ev.target.href.match(/\/topics\/(\d+)\#posts\-last/i);
    if (!topicid)
      return;

    ev.preventDefault();

    findlastPost(topicid[1]);
  };

  // ** "load into view" e.g. use accelerator if #posts-last in address bar
  if (location.hash == "#posts-last") {
    let topicid = location.pathname.match(/\/topics\/(\d+)/i);
    if (topicid) {
      stop();

      findlastPost(topicid[1]);
    }
    else
      history.replaceState({}, '', location.href.replace(/\#posts\-last$/, ""));
  }

  function onDOMContentLoaded() {
    if (!document || !document.body)
      return;

    document.removeEventListener("DOMContentLoaded", onDOMContentLoaded, true);

    if (location.hash == "#posts-last" && location.pathname.match(/\/topics\/\d+$/))
      document.body.style.cursor = "progress";

    let gCSS = GM_setStyle({
        media: "screen, projection"
    });

    if (typeof GM_configStruct != "undefined") {
      // Save some memory
      delete GM_config;

      var gmc = new GM_configStruct();
      gmc.id = "gmc114843";

      var divNode = document.getElementById("full_description");

      if (divNode && !divNode.firstChild) {
        var newdivNode = document.createElement("div");
        divNode = divNode.appendChild(newdivNode);
      }
      else {
        var newdivNode = document.createElement("div");
        if (divNode)
          divNode = divNode.insertBefore(newdivNode, divNode.firstChild);
        else
          divNode = document.body.appendChild(newdivNode);
      }

      /* Nearest fix for a glitch on USO */
      let scriptNav = document.getElementById("script-nav");
      if (scriptNav && divNode && scriptNav.clientWidth != divNode.clientWidth)
        GM_setStyle({
            node: gCSS,
            data:
              [
                "div #full_description { width: 98.1%; }"

              ].join("\n")
        });

      let screenShots = document.getElementById("screenshots");
      if (screenShots)
        GM_setStyle({
            node: gCSS,
            data:
              [
                "#full_description { clear: left; }"

              ].join("\n")
        });

      /* Nearest fix for userscripts.org Alternate CSS */
      let fullDescription = document.getElementById("full_description");
      if (fullDescription && screenShots && fullDescription.clientWidth > parseInt(screenShots.clientWidth * 1.0275))
        GM_setStyle({
            node: gCSS,
            data:
              [
                "#screenshots { width: 97.5% !important; }"

              ].join("\n")
        });

      GM_setStyle({
          media: "print",
          data:
            [
              ".hid { display: none; }",
              "#gmc114843 { display: none !important; }"

            ].join("\n")
      });

      let onInit = function (doc) {
        /* Cleanup unused style in the GMC div portion of the DOM */
        if (doc) {
          let thisNode = doc.getElementById("gmc114843");
          if (thisNode)
            thisNode.removeAttribute("style");
        }
      }

      gmc.init(
          divNode,
//           gmc.onInit, // NOTE: Destruction of this function occurs after initial open with GMC 7e5dfecf87- so don't currently use
          [
              '<img alt="Monkey Barrel" title="uso - Monkey Barrel" src="' + protocol + '//s3.amazonaws.com/uso_ss/icon/114843/large.png" />',
              '<p>Preferences</p>',
              '<span>',
                '<a href="' + protocol + '//github.com/sizzlemctwizzle/GM_config/wiki/">',
                  '<img alt="GM_config" title="Powered in part by GM_config" src="' + GM_getResourceURL("gmc") + '" />',
                '</a>',
              '</span>'

          ].join(""),
          /* Custom CSS */
          GM_setStyle({
              node: null,
              data:
                [
                  /* Homepage */
                  "@media screen, projection {",
                      /* GM_config USO styling fixups */
                      "#gmc114843 { position: static !important; z-index: 0 !important; width: auto !important; height: auto !important; max-height: none !important; max-width: none !important; margin: 0 0 0.5em 0 !important; border: 1px solid #ddd !important; clear: right !important; }",

                      "#gmc114843_header a { display: inline; }",
                      "#gmc114843_header img { max-height: 32px; margin-right: 0.125em; vertical-align: middle; }",
                      "#gmc114843_header > p { display: inline; margin: 0; vertical-align: middle; }",
                      "#gmc114843_header span { float: right; }",
                      "#gmc114843_header span > a { display: inline; margin-left: 0.25em; }",
                      "#gmc114843_wrapper { background-color: #eee; padding-bottom: 0.25em; }",
                      "#gmc114843 .config_header { background-color: #333; color: #fff; font-size: 1.57em; margin: 0; padding: 0 0.5em; text-align: left; }",
                      "#gmc114843 .config_var { clear: both; margin: 0.33em; padding: 0; }",
                      "#gmc114843 .field_label { color: #333; font-size: 100%; font-weight: normal; margin: 0 0.25em; position: relative; top: -0.2em; }",
                      "#gmc114843 .section_header_holder { margin: 0.25em 0.5em !important; }",
                      "#gmc114843 .section_desc { margin: 0.25em 1.5em !important; }",

                          ".gmc-yellownote { background-color: #ffd; font-size: 0.66em !important; }",

                          "#gmc114843_section_header_0 { background-color: inherit !important; border-style: none !important; color: inherit !important; font-size: inherit !important; text-align: left !important; }",
                          "#gmc114843_section_0 { margin: 0 1.5em !important; }",

                          "#gmc114843_jsonMenus_var { margin: -1em 0 !important; }",
                          "#gmc114843_jsonMenus_field_label { margin: 1em 0 !important; }",
                          "#gmc114843_field_jsonMenus { font-size: 1em; height: 15.2em; margin-top: 1em; max-width: 98.68%; min-width: 98.68%; min-height: 15.2em; }",

                      "#gmc114843 .reset, #gmc114843 .reset a, #gmc114843_buttons_holder { text-align: inherit; }",
                      "#gmc114843_buttons_holder { margin: 0.5em; }",
                      "#gmc114843_saveBtn { margin: 0.5em !important; padding: 0 3.0em !important; }",
                      "#gmc114843_resetLink { margin-right: 1.5em; }",
                      "#gmc114843_closeBtn { display: none; }",
                  "}",

                  "@media print {",
                      ".hid { display: none; }",
                      "#gmc114843 { display: none !important; }",
                  "}"

                ].join("\n")
          }),

          /* Settings object */
          {
            'jsonMenus': {
                "section": ["Main menus"],
                "type": 'textarea',
                "label": "<p><em class='gmc-yellownote'>use <a href='http://json.org/'>JSON</a> data-interchange format.</em></p>",
                "default": JSON.stringify(
                             JSON.parse(
                                [
                                  '{',
                                    '"Monkey Barrel": [',
                                      '"/scripts/show/114843",',
                                      '{',
                                      '"recent posts": "/posts",',
                                      '"recent topics": "/topics",',
                                      '"recent comments": "/comments",',
                                      '"recent images": "/images",',
                                      '"": "",',
                                      '"recent spam": "/spam",',
                                      '"recent potential spam": "/posts?kind=all&spam=1",',
                                      '"recent potential spam by score": "/posts?kind=all&spam=score",',
                                      '"spam and malware \u00bb": "/topics/9#posts-last",',
                                      '"cookie stealing scripts \u00bb": "/topics/704#posts-last",',
                                      '" ": "",',
                                      '"custom search": "/search"',
                                      '}',
                                    ']',
                                  '}'

                                ].join("\n")
                             ), null, " ")
            },
            'enableUnpin': {
                "type": 'checkbox',
                "label": 'Unpin submenus',
                "default": true
            },
            'importGroups': {
                "type": 'checkbox',
                "label": 'Enable automatic import for subscribed Groups when clicked',
                "default": true
            },
            'addLastPostLinks': {
                "type": 'checkbox',
                "label": 'Add last post links to the Recent Topics on a script homepage',
                "default": true
            }
          }
      );


      gmc.onOpen = function () {
        onInit(document);

        gmc.fields["jsonMenus"].node.setAttribute("spellcheck", "false");
        gmc.fields["jsonMenus"].node.textContent = JSON.stringify(JSON.parse(gmc.get("jsonMenus")), null, " ");
      }

      gmc.onSave = function () {
        try {
          gmc.set("jsonMenus", JSON.stringify(JSON.parse(gmc.get("jsonMenus")), null, ""));

          gmc.write();
          gmc.close();
          gmc.open();
        }
        catch (e) {
          alert('ERROR: Invalid JSON for main menu.\n\nPlease correct or reset to defaults');
        }
      }

      if (location.pathname.match(/^\/scripts\/show\/114843/i)) {
        gmc.open(); // NOTE: First open
      }

      // -------------------------------------------------------------------------------------------------------------------------------------------------
      GM_setStyle({
        node: gCSS,
        data:
          [
            /* Fix USO */
            "#header #mainmenu { padding-top: 0; }",

            ".hid { display: none; }",
            ".mainmenu- { position: fixed; z-index: 1; margin: 0; list-style: none outside none; border-left: 1px solid #888; border-right: 1px solid #888; border-bottom: 1px solid #888; padding-left: 0; }",
            ".mainmenu- li { -moz-border-radius: 0 !important; border-radius: 0 !important; margin: 0 !important; float: none !important; background: #000 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAZCAQAAABamYz0AAAAAXNSR0IArs4c6QAAAB5JREFUCNdjuOfAxPCPieEvDP1D4v5DIv/iEEcIAgClTRkR4R/Z1AAAAABJRU5ErkJggg==) repeat-x scroll left top !important; }",
            ".mainmenu- li a { color: #fff !important; }",
            ".mainmenu- .sep { border-bottom: thin dashed #aaa !important; }"

          ].join("\n")
      });

      function onresize(ev) {
        let xpr = document.evaluate(
          "//div[@id='top']/div[@class='container']",
          document.body,
          null,
          XPathResult.FIRST_ORDERED_NODE_TYPE,
          null
        );
        if (xpr && xpr.singleNodeValue) {
          let thisNode = xpr.singleNodeValue;

          let width = parseFloat(getComputedStyle(thisNode, null).getPropertyValue("width").replace(/px$/i, "")); // NOTE: Returns normalized used instead of computed
          if (width <= 950) {
            let mainmenu = document.getElementById("mainmenu");
            if (mainmenu)
              mainmenu.style.setProperty("margin-right", (document.body.clientWidth - width) / 2 + "px", "");
          }
          else
            return true;

        }
        if (!ev)
          return false;
      }

      if (gmc.get("enableUnpin")) {
        GM_setStyle({
          node: gCSS,
          data:
            [
              "#header > .container { position: static; }",
              ".mainmenu- { position: absolute; }"

            ].join("\n")
        });

      if (!onresize())
        addEventListener("resize", onresize, false);
      }

      // ** Event listeners
      function onmouseover(ev) {

        let thatNode = this.firstChild.nextSibling;
        while (thatNode) {
          if (thatNode.tagName.toLowerCase() == "ul")
            thatNode.classList.remove("hid");

          thatNode = thatNode.nextSibling;
        }
      };

      function onmouseout(ev) {
        let thatNode = this.firstChild.nextSibling;
        while (thatNode) {
          if (thatNode.tagName.toLowerCase() == "ul")
            thatNode.classList.add("hid");

          thatNode = thatNode.nextSibling;
        }
      };

      // ** Generator functions
      function createMenuItem(aTextContent, aHref) {
        if (aTextContent.trim() == "")
          return undefined;

        let aNode = document.createElement("a");
        aNode.href = aHref;
        aNode.textContent = aTextContent;

        if (aNode.href.match(/\/topics\/\d+\#posts\-last$/i))
          aNode.addEventListener("click", lastPost, false);

        let liNode = document.createElement("li");

        return liNode.appendChild(aNode).parentNode;
      }

      function createMenuItems(aList, aItems) {
        for (let item in aItems) {
          let menuItem = createMenuItem(item,aItems[item]);
          if (menuItem)
            aList.appendChild(menuItem);
          else if (aList.lastChild)
            aList.lastChild.classList.add("sep");
        }

        return aList;
      }

      function createMenu(aMenu) {
        let ulNode = document.createElement("ul");
        ulNode.className = "mainmenu-";
        ulNode.classList.add("hid");

        return createMenuItems(ulNode, aMenu[1]);
      }

      // ** Retrieve the stored menus
      let mainmenu;
      try {
        mainmenu = JSON.parse(gmc.get("jsonMenus"));
      }
      catch (e) {
        if (location.pathname != "/scripts/show/114843") {
          alert('ERROR: Invalid JSON for main menu found in uso - Monkey Barrel.\n\nPlease correct or reset to defaults');

          location.pathname = "/scripts/show/114843";
        }
        return;
      }

      // If on /groups and allowed then read in values from sidebar
      if (gmc.get("importGroups") && location.pathname == "/groups") {
        let xpr = document.evaluate(
              "//div[@id='right']/h3[starts-with(.,'Groups you created')]/following-sibling::ul[1]/li/a"
            + "|//div[@id='right']/h3[starts-with(.,'Groups you joined')]/following-sibling::ul[1]/li/a",
            document.body,
            null,
            XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
            null
        );

        if (xpr.snapshotLength > 0) {
          if (!mainmenu["Groups"])
            mainmenu["Groups"] = [ "/groups", {} ];

          for (let i = 0, thisNode; thisNode = xpr.snapshotItem(i++);) {
            mainmenu["Groups"][1][thisNode.textContent] = thisNode.pathname;
          }

          // Resave the JSON menus silently
          gmc.set("jsonMenus", JSON.stringify(mainmenu, null, ""));

          gmc.write();

          if (gmc.isOpen)
            gmc.fields["jsonMenus"].node.textContent = JSON.stringify(JSON.parse(gmc.get("jsonMenus")), null, " ");
        }
      }


      if (gmc.get("addLastPostLinks")) {
        GM_setStyle({
          node: gCSS,
          data:
            [
              "#topics .postslast { padding: 0.125em 0.25em 0.125em 0.5em; }",
              "#topics td.count { text-align: right; background-color: #eee; }"

            ].join("\n")
        });

        let xpr = document.evaluate(
            "//div[@id='topics']//td/a[starts-with(@href, '/topics')]",
            document.body,
            null,
            XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
            null
        );
        if (xpr && xpr.snapshotLength > 0) {
          // NOTE: Assume table headers are present
          for (let i = 0, thisNode; thisNode = xpr.snapshotItem(i++);) {
            let aNode = document.createElement("a");
            aNode.href = thisNode.href;
            aNode.hash = "posts-last";
            aNode.title = "last post";
            aNode.textContent = thisNode.parentNode.nextSibling.textContent + "\u00a0\u00a0\u00bb";
            aNode.classList.add("lastpost");  // NOTE: USO native
            aNode.classList.add("postslast");
            aNode.addEventListener("click", lastPost, false);

            thisNode.parentNode.nextSibling.removeChild(thisNode.parentNode.nextSibling.firstChild);

            aNode = thisNode.parentNode.parentNode.lastChild.appendChild(aNode);
//            thisNode.parentNode.parentNode.lastChild.insertBefore(aNode, thisNode.parentNode.parentNode.lastChild.firstChild);
          }
        }
      }

      // ** Paint the menus
      let mm = document.getElementById("mainmenu");
      if (mm) {
        // Remove Jetpacks mainmenu item, if present. from userscripts alternate CSS
        let xpr = document.evaluate(
            "./li/a[.='Jetpacks']",
            mm,
            null,
            XPathResult.FIRST_ORDERED_NODE_TYPE,
            null
        );
        if (xpr && xpr.singleNodeValue) {
          let thisNode = xpr.singleNodeValue;

          thisNode.parentNode.parentNode.removeChild(thisNode.parentNode);
        }

        // Twiddle
        document.evaluate(
            "./li/a[.='Monkey Barrel']",
            mm,
            null,
            XPathResult.FIRST_ORDERED_NODE_TYPE,
            xpr
        );
        if (xpr && xpr.singleNodeValue)
          return;

        mm.appendChild(createMenuItem("Monkey Barrel", "/scripts/show/114843"));

        // Get existing menu items and then send off to functions
        document.evaluate(
            "./li",
            mm,
            null,
            XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
            xpr
        );

        for (let i = 0, thisNode; thisNode = xpr.snapshotItem(i++);) {
          let aNode = thisNode.firstChild;

          if (mainmenu[aNode.textContent]) {
            thisNode.appendChild(createMenu(mainmenu[aNode.textContent]));
            thisNode.addEventListener("mouseover", onmouseover, false);
            thisNode.addEventListener("mouseout", onmouseout, false);
          }
        }
      }

    }
  }

  document.addEventListener("DOMContentLoaded", onDOMContentLoaded, true);

})();