Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[issue]-[GitHub collapse markdown] Contentless-headers are collapsible #79

Closed
oueV opened this issue Mar 8, 2019 · 2 comments
Closed

Comments

@oueV
Copy link

oueV commented Mar 8, 2019

Many Github repo owner seems to systematically add an H3 header (###) to all of their hyperlinks..
While this has no particular impact for those not using the "Github collapse markdown" userscript,
it does for all who does. Since any header are made collapsible by this userscript, even without anything content to collapse are made collapsible as well..

I have to admit this is not a bug/issue of this userscript, rather a wrong practice from those github users... but the practice seems to be so wide that a fix for this userscript seems necessary..

concrete exemple:
https://github.com/iperov/DeepFaceLab/blob/master/README.md

Proposal:
Adding a user-configurable conditional minimum number of lines to be preset under any header.

@Mottie
Copy link
Owner

Mottie commented Mar 9, 2019

Hi @oueV!

I threw together this modification; please test it out and let me know if it's working as expected:

// ==UserScript==
// @name        GitHub Collapse Markdown
// @version     1.2.0
// @description A userscript that collapses markdown headers
// @license     MIT
// @author      Rob Garrison
// @namespace   https://github.com/Mottie
// @include     https://github.com/*
// @include     https://gist.github.com/*
// @include     https://help.github.com/*
// @run-at      document-idle
// @grant       GM_addStyle
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_registerMenuCommand
// @require     https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=666427
// @icon        https://github.githubassets.com/pinned-octocat.svg
// @updateURL   https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-collapse-markdown.user.js
// @downloadURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-collapse-markdown.user.js
// ==/UserScript==
(() => {
  "use strict";

  const defaultColors = [
      // palette generated by http://tools.medialab.sciences-po.fr/iwanthue/
      // (colorblind friendly, soft)
      "#6778d0",
      "#ac9c3d",
      "#b94a73",
      "#56ae6c",
      "#9750a1",
      "#ba543d"
    ],
    blocks = [
      ".markdown-body",
      ".markdown-format",
      "" // leave empty string at the end
    ],
    headers = "H1 H2 H3 H4 H5 H6".split(" "),
    collapsed = "ghcm-collapsed",
    arrowColors = document.createElement("style");

  let startCollapsed = GM_getValue("ghcm-collapsed", false),
    colors = GM_getValue("ghcm-colors", defaultColors);

  // .markdown-body h1:after, .markdown-format h1:after, ... {}
  GM_addStyle(`
		${blocks.join(" h1,")} ${blocks.join(" h2,")}
		${blocks.join(" h3,")} ${blocks.join(" h4,")}
		${blocks.join(" h5,")} ${blocks.join(" h6,").slice(0, -1)} {
			position:relative;
			padding-right:.8em;
			cursor:pointer;
		}
		${blocks.join(" h1:after,")} ${blocks.join(" h2:after,")}
		${blocks.join(" h3:after,")} ${blocks.join(" h4:after,")}
		${blocks.join(" h5:after,")} ${blocks.join(" h6:after,").slice(0, -1)} {
			display:inline-block;
			position:absolute;
			right:0;
			top:calc(50% - .5em);
			font-size:.8em;
			content:"\u25bc";
		}
		${blocks.join(" ." + collapsed + ":after,").slice(0, -1)} {
			transform: rotate(90deg);
		}
		/* clicking on header link won't pass svg as the event.target */
		.octicon-link, .octicon-link > * {
			pointer-events:none;
		}
		.ghcm-hidden, .ghcm-no-content:after {
			display:none !important;
		}
	`);

  function addColors() {
    let sel,
      styles = "";
    headers.forEach((header, indx) => {
      sel = `${blocks.join(" " + header + ":after,").slice(0, -1)}`;
      styles += `${sel} { color:${colors[indx]} }`;
    });
    arrowColors.textContent = styles;
  }

  function toggle(el, shifted) {
    if (el && !el.classList.contains("ghcm-no-content")) {
      el.classList.toggle(collapsed);
      let els;
      const name = el.nodeName || "",
        // convert H# to #
        level = parseInt(name.replace(/[^\d]/, ""), 10),
        isCollapsed = el.classList.contains(collapsed);
      if (shifted) {
        // collapse all same level anchors
        els = $(`${blocks.join(" " + name + ",").slice(0, -1)}`);
        for (el of els) {
          nextHeader(el, level, isCollapsed);
        }
      } else {
        nextHeader(el, level, isCollapsed);
      }
      removeSelection();
    }
  }

  function nextHeader(el, level, isCollapsed) {
    el.classList.toggle(collapsed, isCollapsed);
    const selector = headers.slice(0, level).join(","),
      name = [collapsed, "ghcm-hidden"],
      els = [];
    el = el.nextElementSibling;
    while (el && !el.matches(selector)) {
      els[els.length] = el;
      el = el.nextElementSibling;
    }
    if (els.length) {
      if (isCollapsed) {
        els.forEach(el => {
          el.classList.add("ghcm-hidden");
        });
      } else {
        els.forEach(el => {
          el.classList.remove(...name);
        });
      }
    }
  }

  // show siblings of hash target
  function siblings(target) {
    let el = target.nextElementSibling,
      els = [target];
    const level = parseInt((target.nodeName || "").replace(/[^\d]/, ""), 10),
      selector = headers.slice(0, level - 1).join(",");
    while (el && !el.matches(selector)) {
      els[els.length] = el;
      el = el.nextElementSibling;
    }
    el = target.previousElementSibling;
    while (el && !el.matches(selector)) {
      els[els.length] = el;
      el = el.previousElementSibling;
    }
    if (els.length) {
      els = els.filter(el => {
        return el.nodeName === target.nodeName;
      });
      for (el of els) {
        el.classList.remove("glcm-hidden");
      }
    }
    nextHeader(target, level, false);
  }

  function removeSelection() {
    // remove text selection - https://stackoverflow.com/a/3171348/145346
    const sel = window.getSelection
      ? window.getSelection()
      : document.selection;
    if (sel) {
      if (sel.removeAllRanges) {
        sel.removeAllRanges();
      } else if (sel.empty) {
        sel.empty();
      }
    }
  }

  function addBinding() {
    document.addEventListener("click", event => {
      let target = event.target;
      const name = (target && (target.nodeName || "")).toLowerCase();
      if (name === "path") {
        target = target.closest("svg");
      }
      if (
        !target ||
        target.classList.contains("anchor") ||
        name === "a" ||
        name === "img" ||
        // add support for "pointer-events:none" applied to "anchor" in
        // https://github.com/StylishThemes/GitHub-FixedHeader
        target.classList.contains("octicon-link")
      ) {
        return;
      }
      // check if element is inside a header
      target = event.target.closest(headers.join(","));
      if (target && headers.indexOf(target.nodeName || "") > -1) {
        // make sure the header is inside of markdown
        if (target.closest(blocks.slice(0, -1).join(","))) {
          toggle(target, event.shiftKey);
        }
      }
    });
    document.addEventListener("ghmo:container", () => {
      // init after a short delay to allow rendering of file list
      setTimeout(() => {
        ignoreEmptyHeaders();
      }, 200);
    });
  }

  function checkHash() {
    let el, els, md;
    const mds = $(blocks.slice(0, -1).join(",")),
      id = (window.location.hash || "").replace(/#/, "");
    for (md of mds) {
      els = $(headers.join(","), md);
      if (els.length > 1) {
        for (el of els) {
          if (el && !el.classList.contains(collapsed)) {
            toggle(el, true);
          }
        }
      }
    }
    if (id) {
      openHash(id);
    }
  }

  // open header matching hash
  function openHash(id) {
    const els = $(`#user-content-${id}`);
    if (els && els.classList.contains("anchor")) {
      let el = els.parentNode;
      if (el.matches(headers.join(","))) {
        siblings(el);
        document.documentElement.scrollTop = el.offsetTop;
        // set scrollTop a second time, in case of browser lag
        setTimeout(() => {
          document.documentElement.scrollTop = el.offsetTop;
        }, 500);
      }
    }
  }

  function checkColors() {
    if (!colors || colors.length !== 6) {
      colors = [].concat(defaultColors);
    }
  }

  function ignoreEmptyHeaders() {
    $("a.anchor").forEach(el => {
      const parent = el.parentNode;
      if (
        parent &&
        parent.matches(headers.join(",")) &&
        !parent.nextElementSibling
      ) {
        parent.classList.add("ghcm-no-content");
      }
    });
  }

  function init() {
    document.querySelector("head").appendChild(arrowColors);
    checkColors();
    addColors();
    addBinding();
    ignoreEmptyHeaders();
    if (startCollapsed) {
      checkHash();
    }
  }

  function $(selector, el) {
    return (el || document).querySelector(selector);
  }

  function $(selectors, el) {
    return [...(el || document).querySelectorAll(selectors)];
  }

  // Add GM options
  GM_registerMenuCommand("Set collapse markdown state", () => {
    const val = prompt(
      "Set initial state to (c)ollapsed or (e)xpanded (first letter necessary):",
      startCollapsed ? "collapsed" : "expanded"
    );
    if (val !== null) {
      startCollapsed = /^c/i.test(val);
      GM_setValue("ghcm-collapsed", startCollapsed);
      console.log(
        `GitHub Collapse Markdown: Headers will ${startCollapsed
          ? "be"
          : "not be"} initially collapsed`
      );
    }
  });

  GM_registerMenuCommand("Set collapse markdown colors", () => {
    let val = prompt("Set header arrow colors:", JSON.stringify(colors));
    if (val !== null) {
      // allow pasting in a JSON format
      try {
        val = JSON.parse(val);
        if (val && val.length === 6) {
          colors = val;
          GM_setValue("ghcm-colors", colors);
          console.log("GitHub Collapse Markdown: colors set to", colors);
          addColors();
          return;
        }
        console.error(
          "GitHub Collapse Markdown: invalid color definition (6 colors)",
          val
        );
        // reset colors to default (in case colors variable is corrupted)
        checkColors();
      } catch (err) {
        console.error("GitHub Collapse Markdown: invalid JSON");
      }
    }
  });

  init();
})();

@oueV
Copy link
Author

oueV commented Apr 18, 2019

Hi @Mottie
Sorry for the so late reply !
Saw your fix. Tested. All seems perfect here ! Thanks !!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants