table-copier

适用于任意网站,快速复制表格为纯文本、HTML、图片

目前为 2023-06-27 提交的版本。查看 最新版本

// ==UserScript==
// @name            table-copier
// @namespace       http://tampermonkey.net/
// @version         0.1
// @description     适用于任意网站,快速复制表格为纯文本、HTML、图片
// @match           *://*/*
// @require         https://cdn.staticfile.org/html2canvas/1.4.1/html2canvas.min.js
// @grant           none
// @run-at          document-idle
// @license         GPL-3.0-only
// @create          2023-06-27
// ==/UserScript==


(function() {
    "use strict";

    const SCRIPTS = [
        ["html2canvas", "https://cdn.staticfile.org/html2canvas/1.4.1/html2canvas.min.js"]
    ];
    const BTN = `<button class="never-gonna-give-you-up" style="width: 70px; height: 30px;" onclick="copy_table(this)">复制表格</button>`;
    const COPY_GAP = 500;

    /**
     * 元素选择器
     * @param {string} selector 
     * @returns {Array<HTMLElement>}
     */
    function $(selector) {
        const self = this?.querySelectorAll ? this : document;
        return [...self.querySelectorAll(selector)];
    }

    /**
     * 异步等待delay毫秒
     * @param {number} delay 
     * @returns 
     */
    function sleep(delay) {
        return new Promise(resolve => setTimeout(resolve, delay));
    }

    /**
     * 加载CDN脚本
     * @param {string} url 
     */
    async function load_script(url) {
        try {
            const code = await (await fetch(url)).text();
            Function(code)();
        } catch(e) {
            return new Promise(resolve => {
                console.error(e);
                // 嵌入<script>方式
                const script = document.createElement("script");
                script.src = url;
                script.onload = resolve;
                document.body.appendChild(script);
            })
        }
    }

    async function until_scripts_loaded() {
        return gather(SCRIPTS.map(
            // kv: [prop, url]
            kv => (async () => {
                if (window[kv[0]]) return;
                await load_script(kv[1]);
            })()
        ));
    }

    /**
     * 等待全部任务落定后返回值的列表
     * @param {Iterable<Promise>} tasks 
     * @returns {Promise<Array>} values
     */
    async function gather(tasks) {
        const results = await Promise.allSettled(tasks);
        const filtered = [];
        for (const result of results) {
            if (result.value) {
                filtered.push(result.value);
            }
        }
        return filtered;
    }

    /**
     * 递归的修正表内元素
     * @param {HTMLElement} elem 
     */
    function adjust_table(elem) {
        for (const child of elem.children) {
            adjust_table(child);

            for (const attr of child.attributes) {
                // 链接补全
                const name = attr.name;
                if (["src", "href"].includes(name)) {
                    child.setAttribute(name, child[name]);
                }
            }
        }
    }

    /**
     * 复制媒体到剪贴板
     * @param {string | ArrayBuffer | ArrayBufferView} data 
     * @param {string} type
     * @returns {ClipboardItem} 
     */
    function to_clipboarditem(data, type) {
        const blob = new Blob([data], { type: type });
        const item = new ClipboardItem({ [type]: blob });
        return item;
    }

    /**
     * canvas转blob
     * @param {HTMLCanvasElement} canvas 
     * @param {string} type
     * @returns {Promise<Blob>}
     */
    function canvas_to_blob(canvas, type="image/png") {
        return new Promise(
            resolve => canvas.toBlob(resolve, type, 1)
        );
    }


    /**
     * 复制表格为纯文本到剪贴板
     * @param {HTMLTableElement} table 
     * @returns {Promise<ClipboardItem>} 
     */
    async function copy_table_as_text(table) {
        console.log("table to text");

        // table 转 tsv 格式文本
        const data = [...table.rows].map(
            row => [...row.cells].map(
                cell => cell.textContent.replace(/\t/g, "    ")
            ).join("\t")
        ).join("\n");

        console.log(data);
        return to_clipboarditem(data, "text/plain");
    }

    /**
     * 复制表格为html到剪贴板
     * @param {HTMLTableElement} table 
     * @returns {Promise<ClipboardItem>} 
     */
    async function copy_table_as_html(table) {
        console.log("table to html");

        const _table = table.cloneNode(true);
        adjust_table(_table);
        return to_clipboarditem(_table.outerHTML, "text/html");
    }

    /**
     * 复制表格为图片到剪贴板
     * @param {HTMLTableElement} table 
     * @returns {Promise<ClipboardItem>} 
     */
    async function copy_table_as_image(table) {
        console.log("table to image");

        let canvas;
        try {
            canvas = await window.html2canvas(table);
        } catch(e) {
            console.error(e);
        }
        if (!canvas) return;

        const type = "image/png";
        const blob = await canvas_to_blob(canvas, type);
        return to_clipboarditem(blob, type);
    }

    /**
     * 复制表格到剪贴板
     * @param {HTMLButtonElement} btn
     */
    async function copy_table(btn) {
        const table = btn.closest("table");
        if (!table) {
            alert("出错了:按钮外部没有表格");
            return;
        }

        // 移除按钮
        $(".never-gonna-give-you-up").forEach(
            btn => btn.remove()
        );

        const actions = [
            copy_table_as_text,
            copy_table_as_html,
            copy_table_as_image,
        ];
        const items = await gather(actions.map(
            copy => copy(table)
        ));

        try {
            const last_item = items.pop();

            for (const item of items) {
                await navigator.clipboard.write([item]);
                await sleep(COPY_GAP);
            }
            await navigator.clipboard.write([last_item]);
            alert("复制成功!");

        } catch(e) {
            console.error(e);
            alert("复制失败!");
        }

        // 增加按钮
        add_btns();
    }

    function add_btns() {
        for (const table of $("table")) {
            // 跳过隐藏的表格
            if (!table.getClientRects()[0]) continue;
            table.insertAdjacentHTML("afterbegin", BTN);
        }
    }

    (async () => {
        try {
            await until_scripts_loaded();
        } catch(e) {
            console.error(e);
        }

        window.copy_table = copy_table;
        setTimeout(add_btns, 1000);
    })();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址