issues copy

add markdown copy to issues page

// ==UserScript==
// @name            issues copy
// @namespace       https://github.com/zhzLuke96/github-issues-copy-user-js
// @version         1.0.2
// @description:cn     为issues页面添加markdown复制按钮
// @description:en     add markdown copy to issues page
// @author          zhzluke96
// @match           https://*.github.com/*
// @icon            https://www.google.com/s2/favicons?sz=64&domain=github.com
// @grant           none
// @license         MIT
// @supportURL      https://github.com/zhzLuke96/github-issues-copy-user-js/issues
// @description add markdown copy to issues page
// ==/UserScript==

(function () {
  "use strict";

  const _historyWrap = function (type) {
    const orig = history[type];
    const e = new Event(type);
    return function () {
      const rv = orig.apply(this, arguments);
      e.arguments = arguments;
      window.dispatchEvent(e);
      return rv;
    };
  };
  history.pushState = _historyWrap("pushState");
  history.replaceState = _historyWrap("replaceState");

  const html_tpls = {
    btn: (callback) => {
      const btn = renderHtml(
        `<button id="issues_copy_btn" data-component="IconButton" type="button" class="prc-Button-ButtonBase-c50BI prc-Button-IconButton-szpyj" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="invisible" aria-describedby=":r59:-loading-announcement" aria-labelledby=":r57:">
        <svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
  <text x="1" y="11" font-size="10" font-family="Arial, sans-serif" fill="currentColor">MD</text>
</svg>
</button>`
      );
      btn.addEventListener("click", callback);
      btn.title = "copy this page to markdown";
      btn.dataset.check_id = "issues_copy";
      return btn;
    },
  };

  function is_injected() {
    return (
      document.querySelectorAll(`[data-check_id="issues_copy"]`).length > 0
    );
  }

  function get_issues_markdown_content() {
    const issues_elements = [
      `[data-component="PH_Title"]`,
      `[data-testid="issue-viewer-issue-container"]`,
      `[data-testid="issue-timeline-container"]`,
    ];
    return issues_elements
      .map((selector) => document.querySelector(selector))
      .map((element) => htmlElementToMarkdown(element))
      .join("\n\n");
  }

  function is_issues_page() {
    return (
      document.querySelectorAll(`[data-testid="issue-viewer-issue-container"]`)
        .length > 0
    );
  }

  async function wait_for_page_render(timeout_ms = 1000) {
    return new Promise((resolve) => {
      let timer = null;
      const observer = new MutationObserver(() => {
        clearTimeout(timer);
        refresh();
      });
      function refresh() {
        timer = setTimeout(() => {
          observer.disconnect();
          resolve();
        }, timeout_ms);
      }

      observer.observe(document.documentElement, {
        childList: true,
        subtree: true,
        attributes: true,
        characterData: true,
      });
      refresh();
    });
  }

  async function do_inject() {
    await wait_for_page_render();
    if (is_injected()) return;
    if (!is_issues_page()) return;
    const anchor_span = document.querySelector(
      `[data-component="PH_Actions"] [class*="CopyToClipboardButton"]`
    );
    anchor_span.after(
      html_tpls.btn(() => {
        const markdown_content = get_issues_markdown_content();
        const textarea = document.createElement("textarea");
        textarea.value = markdown_content;
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand("copy");
        document.body.removeChild(textarea);
        alert("[issues_copy]copy success");
        console.log("[issues_copy]copy success");
        console.log(markdown_content);
      })
    );
  }

  do_inject();

  window.addEventListener("pushState", () => {
    do_inject();
  });
  window.addEventListener("replaceState", () => {
    do_inject();
  });

  // ----------------------------
  // 拓展区域
  // ----------------------------
  /**
   *
   * @param {HTMLElement} element
   * @returns {boolean}
   */
  function is_hide_element(element) {
    // 判断是否为隐藏
    return (
      element.hasAttribute("hidden") ||
      element.classList.contains("hidden") ||
      element.style.display === "none" ||
      getComputedStyle(element).display === "none" ||
      getComputedStyle(element).visibility === "hidden" ||
      getComputedStyle(element).opacity === "0"
    );
  }

  /**
   *
   * @param {HTMLElement} element
   * @returns {string}
   */
  function htmlElementToMarkdown(element) {
    /**
     *
     * @param {HTMLElement} node
     * @returns string
     */
    function process(node) {
      if (node.nodeType === Node.TEXT_NODE) {
        return node.textContent;
      }

      if (node.nodeType !== Node.ELEMENT_NODE) {
        return "";
      }
      if (is_hide_element(node)) {
        return "";
      }

      const tag = node.tagName.toLowerCase();
      let content = Array.from(node.childNodes).map(process).join("");

      if (content.trim() === "") {
        return "";
      }

      if (node.classList.contains("markdown-body")) {
        content = "\n" + content;
      }

      switch (tag) {
        case "h1":
          return `# ${content}\n\n`;
        case "h2":
          return `## ${content}\n\n`;
        case "h3":
          return `### ${content}\n\n`;
        case "h4":
          return `#### ${content}\n\n`;
        case "h5":
          return `##### ${content}\n\n`;
        case "h6":
          return `###### ${content}\n\n`;
        case "p":
          return `${content}\n\n`;
        case "strong":
        case "b":
          return `**${content}**`;
        case "em":
        case "i":
          return `*${content}*`;
        case "a":
          return `[${content}](${node.getAttribute("href")})`;
        case "code": {
          const code_content = node.innerText;
          return `\`${code_content}\``;
        }
        case "pre": {
          // 尝试查找代码块的语言 父元素上的 `highlight-source-xxx` 就是语言
          const lang =
            Array.from(node.parentElement.classList)
              .find((className) => className.startsWith("highlight-source-"))
              ?.replace("highlight-source-", "") ?? "";
          const code_content = node.innerText;
          return `\n\`\`\`${lang}\n${code_content}\n\`\`\`\n`;
        }
        case "ul":
          return (
            "\n" +
            Array.from(node.children)
              .map((li) => `- ${process(li)}`)
              .join("\n") +
            "\n\n"
          );
        case "ol":
          return (
            "\n" +
            Array.from(node.children)
              .map((li, i) => `${i + 1}. ${process(li)}`)
              .join("\n") +
            "\n\n"
          );
        case "br":
          return "  \n";
        case "blockquote":
          return "> " + content.replace(/\n/g, "\n> ") + "\n\n";
        case "img":
          const alt = node.getAttribute("alt") || "";
          const src = node.getAttribute("src") || "";
          return `![${alt}](${src})`;
        default:
          return content;
      }
    }

    return process(element).trim();
  }

  /**
   *
   * @param {string} html_content
   * @returns {HTMLElement}
   */
  function renderHtml(html_content) {
    const container = document.createElement("div");
    container.innerHTML = html_content;
    return container.children[0];
  }
})();
长期地址
遇到问题?请前往 GitHub 提 Issues,或加Q群1031348184

赞助商

Fishcpy

广告

Rainyun

注册一下就行

Rainyun

一年攒够 12 元