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