您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
获取页面 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(); } })();