copy-markdown

获取页面 Markdown 源代码

// ==UserScript==
// @name         copy-markdown
// @namespace    https://github.com/LYkcul
// @description  获取页面 Markdown 源代码
// @author       BlackPanda
// @license      MIT
// @version      1.2.0
// @match        https://www.luogu.com.cn/*
// @match        https://www.luogu.com/*
// @grant        none
// ==/UserScript==

(function () {
  "use strict";
  function injectStyle() {
    var style = document.createElement("style");
    style.textContent = `
    .lcolor-var-green-3 {
        --lcolor-rgb: 82, 196, 26;
    }
    .lcolor-var-blue-3 {
        --lcolor-rgb: 52, 152, 219;
    }
    .lcolor-var-red-3 {
        --lcolor-rgb: 231, 76, 60;
    }   
    .copy-markdown-btn {
        --l-button--real-color: var(--lcolor-rgb, var(--lcolor--primary, var(--lcolor--primary, 52, 152, 219)));
        display: inline-block;
        outline: none;
        cursor: pointer;
        font-weight: inherit;
        line-height: 1.5;
        text-align: center;
        vertical-align: middle;
        border-radius: 3px;
        border: 1px solid rgb(var(--l-button--real-color));
        background: rgba(var(--l-button--real-color),0) none;
        color: rgb(var(--l-button--real-color));
        margin-right: .5em;
    }
    .copy-markdown-btn-contest {
        --l-button--real-color: var(--lcolor-rgb, var(--lcolor--primary, var(--lcolor--primary, 52, 152, 219)));
        display: inline-block;
        outline: none;
        cursor: pointer;
        font-weight: inherit;
        line-height: 1.5;
        text-align: center;
        vertical-align: middle;
        border-radius: 3px;
        border: 1px solid rgb(var(--l-button--real-color));
        background: rgb(var(--l-button--real-color));
        color: #fff;
        margin-right: .5em;
    }
    /* why no margin-right on exlg-cph??? */
    .exlg-cph + .copy-markdown-btn-contest {
        margin-left: .5em;
    }
    `;
    document.head.appendChild(style);
  }
  /*====================*/
  const routes = [
    {
      name: "article_show",
      match: (path) => /^\/(article)\/(?!mine$|_?new$)[A-Za-z0-9]+$/.test(path),
      mount: ".user-nav",
      getContent: async () => {
        let ele = document.querySelector("script#lentille-context");
        let json = JSON.parse(ele.textContent.trim());
        let url = location.href;
        if (url.endsWith("/")) url = url.slice(0, -1);
        if (
          json.template !== "article.show" ||
          json.data.article.lid !== url.split("/").pop()
        ) {
          console.log("[DEBUG] retry");
          const res = await fetch(location.href, {
            credentials: "include",
            headers: {
              "x-requested-with": "XMLHttpRequest",
              "x-lentille-request": "content-only",
            },
          });
          const html = await res.text();
          json = JSON.parse(html.trim());
        }
        console.log(json);
        const content = json.data?.article?.content || "复制失败";
        return content;
      },
    },
    {
      name: "user_detail",
      match: (path) => /^\/user\/\d+$/.test(path),
      test: (url) => !(url.hash && url.hash.length > 1 && url.hash !== "#main"),
      mount: ".user-nav",
      getContent: async () => {
        return _feInstance.currentData.user.introduction || "复制失败";
      },
      className: "lfe-form-sz-middle lcolor-var-blue-3 copy-markdown-btn",
      classNameSucc: "lfe-form-sz-middle lcolor-var-green-3 copy-markdown-btn",
      classNameErr: "lfe-form-sz-middle lcolor-var-red-3 copy-markdown-btn",
    },
    {
      name: "contest_detail",
      match: (path) => /^\/contest\/\d+$/.test(path),
      test: (url) =>
        !(url.hash && url.hash.length > 1 && url.hash !== "#description"),
      mount: ".functional > .operation",
      specialMount: true,
      getContent: async () => {
        return _feInstance.currentData.contest.description || "复制失败";
      },
      className:
        "lfe-form-sz-middle lcolor-var-blue-3 copy-markdown-btn-contest",
      classNameSucc:
        "lfe-form-sz-middle lcolor-var-green-3 copy-markdown-btn-contest",
      classNameErr:
        "lfe-form-sz-middle lcolor-var-red-3 copy-markdown-btn-contest",
      selfMount: ".copy-markdown-btn-contest",
    },
    {
      name: "training_detail",
      match: (path) => /^\/training\/\d+$/.test(path),
      test: (url) =>
        !(url.hash && url.hash.length > 1 && url.hash !== "#information"),
      mount: ".functional > .operation",
      specialMount: true,
      getContent: async () => {
        return _feInstance.currentData.training.description || "复制失败";
      },
      className:
        "lfe-form-sz-middle lcolor-var-blue-3 copy-markdown-btn-contest",
      classNameSucc:
        "lfe-form-sz-middle lcolor-var-green-3 copy-markdown-btn-contest",
      classNameErr:
        "lfe-form-sz-middle lcolor-var-red-3 copy-markdown-btn-contest",
      selfMount: ".copy-markdown-btn-contest",
    },
  ];
  function getRoute() {
    const path = location.pathname;
    return (
      routes.find((r) => {
        return (
          (typeof r.test === "function" ? r.test(location) : true) &&
          r.match(path)
        );
      }) || null
    );
  }
  function isValidUrl() {
    return !!getRoute();
  }
  function cleanupBtn() {
    document
      .querySelectorAll("#copy-markdown-mount")
      .forEach((el) => el.remove());
  }
  async function insertBtn() {
    const route = getRoute();
    if (!route) return false;
    const nav = document.querySelector(route.mount || ".user-nav");
    if (!nav) return;
    if (nav.querySelector(".copy-markdown-btn")) return true;
    let btn = document.createElement("button");
    btn.textContent = "复制源代码";
    btn.type = "button";
    btn.id = "copy-markdown-mount";
    btn.className =
      route.className ||
      "lform-size-middle lcolor-var-blue-3 copy-markdown-btn";
    btn.addEventListener("click", async () => {
      try {
        console.log("[DEBUG] route: ", route.name);
        const content = await route.getContent();
        await navigator.clipboard.writeText(content);
        btn.textContent = "复制成功";
        btn.className =
          route.classNameSucc ||
          "lform-size-middle lcolor-var-green-3 copy-markdown-btn";
      } catch (e) {
        console.error("复制失败:", e);
        btn.textContent = "复制失败";
        btn.className =
          route.classNameErr ||
          "lform-size-middle lcolor-var-red-3 copy-markdown-btn";
      } finally {
        setTimeout(() => {
          btn.textContent = "复制源代码";
          btn.className =
            route.className ||
            "lform-size-middle lcolor-var-blue-3 copy-markdown-btn";
        }, 600);
      }
    });
    if (route.specialMount) {
      nav.insertBefore(btn, nav.lastChild.nextSibling);
    } else {
      nav.insertBefore(btn, nav.firstChild);
    }
    return true;
  }
  function handleUrlChange() {
    injectStyle();
    if (!isValidUrl()) {
      cleanupBtn();
      return;
    }
    const start = Date.now();
    const timer = setInterval(() => {
      if (!isValidUrl()) {
        clearInterval(timer);
        cleanupBtn();
        return;
      }
      if (insertBtn()) {
        clearInterval(timer);
        return;
      }
      if (Date.now() - start > 3000) clearInterval(timer);
    }, 100);
  }
  const fireUrlChange = () =>
    window.dispatchEvent(new Event("userscript:urlchange"));
  const _ps = history.pushState,
    _rs = history.replaceState;
  history.pushState = function (...a) {
    const r = _ps.apply(this, a);
    fireUrlChange();
    return r;
  };
  history.replaceState = function (...a) {
    const r = _rs.apply(this, a);
    fireUrlChange();
    return r;
  };
  window.addEventListener("popstate", fireUrlChange);
  window.addEventListener("hashchange", fireUrlChange);
  window.addEventListener("userscript:urlchange", () => {
    cleanupBtn();
    setTimeout(handleUrlChange, 0);
  });
  const init = () => handleUrlChange();
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();