适用于任意网站,快速复制表格为纯文本、HTML、图片
当前为
// ==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);
})();
})();