Table copier (to clipboard)

Choose from your userscipts addon menu. All tables are highlighted, click one to copy, elsewhere to cancel. Two options: 1. as HTML, 2. HTML as text, with links replaced with their addresses. Pasteable into spreadsheets like Excel, Google Sheets, LibreOffice Calc, OpenOffice Calc and others.

目前為 2024-07-07 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Table copier (to clipboard)
// @version      0.3
// @description  Choose from your userscipts addon menu. All tables are highlighted, click one to copy, elsewhere to cancel. Two options: 1. as HTML, 2. HTML as text, with links replaced with their addresses. Pasteable into spreadsheets like Excel, Google Sheets, LibreOffice Calc, OpenOffice Calc and others.
// @author       Jakub Marcinkowski <kuba.marcinkowski on g mail>
// @copyright    2024+, Jakub Marcinkowski <kuba.marcinkowski on g mail>
// @license      Zlib
// @namespace    Jakub Marcinkowski
// @homepageURL  https://gist.github.com/JakubMarcinkowski
// @homepageURL  https://github.com/JakubMarcinkowski
// @match        *://*/*
// @noframes
// @run-at       document-idle
// @grant        GM.registerMenuCommand
// @grant        GM.setClipboard
// ==/UserScript==

(function() {
  'use strict';

  if (!('setClipboard' in GM)) return;

  let cleaned = 1;
  let tables = [];
  let tablesBorders = new Map();

  function run() {
    tables = [...document.getElementsByTagName('table')];
    if (tables.length === 1) {
      copyTable(tables[0]);
      tables[0].scrollIntoView({behavior: "smooth", block: "nearest", inline: "nearest"});
      borderHighlight(tables[0]);
      setTimeout(borderNormal, 1000, tables[0]);
    } else {
      tables.forEach(elem => {
        borderHighlight(elem);
        elem.addEventListener('click', takeElem, {capture: true});
        elem.addEventListener('contextmenu', takeElem, {capture: true});
      });
      document.addEventListener('click', stopAll);
    }
  }

  function end() {
    tables.forEach(elem => {
      borderNormal(elem);
      elem.removeEventListener('click', takeElem, {capture: true});
      elem.removeEventListener('contextmenu', takeElem, {capture: true});
    });
    document.removeEventListener('click', stopAll);
    tables = [];
    tablesBorders = new Map();
  }

  function stopAll(e) {
    e.preventDefault();
    e.stopPropagation();
    end();
  }

  function takeElem(e) {
    stopAll(e);
    const table = e.target.tagName === 'TABLE' ? e.target : e.target.closest('table');
    if (table) copyTable(table);
  }

  function borderHighlight(elem) {
    tablesBorders.set(elem, elem.style.border);
    elem.style.border = '2px dashed green';
  }

  function borderNormal(elem) {
    elem.style.border = tablesBorders.get(elem);
  }

  function processTable(table) {
    // When pasting tags from these long selectors (except style), Excel makes additional rows.
    let clone = table.cloneNode(true);
    clone.querySelectorAll('br, wbr, hr, img, style')
    .forEach(elem => {
      elem.remove();
    });
    if (cleaned >= 2) {
      clone.querySelectorAll('a')
      .forEach(link => {
        const span = document.createElement('span');
        span.textContent = link.href;
        link.replaceWith(span);
        span.after(' '); // Separate texts (hrefs)
      });
    }
    if (cleaned === 3) {
      clone.querySelectorAll('*')
      .forEach(elem => {
        if (['SPAN', 'TBODY', 'THEAD', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL'].includes(elem.tagName)) return;
        const span = document.createElement('span');
        for (let child of elem.childNodes) {
          span.append(child);
        }
        elem.replaceWith(span);
      });
    } else {
      clone.querySelectorAll('p, div, h1, h2, h3, h4, h5, h6, ul, li, ol, dl, dt, dd')
      .forEach(elem => {
        const span = document.createElement('span');
        for (let child of elem.childNodes) {
          span.append(child);
        }
        for (let attr of elem.getAttributeNames()) {
          span.setAttribute(attr, elem.getAttribute(attr));
        }
        elem.replaceWith(span);
      });
    }
    return clone;
  }

  function copyTable(table) {
    if (cleaned !== 0) table = processTable(table);
    let param;
    switch (GM.info.scriptHandler) {
      case 'Tampermonkey':
        param = 'html'
        break;
      case 'Violentmonkey':
      case 'Userscripts':
        param = 'text/html'
        break;
      case 'Greasemonkey':
      default:
        param = null;
    }
    if (param) GM.setClipboard(table.outerHTML, param);
    else GM.setClipboard(table.outerHTML);
    // alert(`Table in clipboard. Now paste it into a spreadsheet.`);
  }

  GM.registerMenuCommand('Copy table (full HTML)', () => {cleaned = 0; run();});
  GM.registerMenuCommand('Copy table (cleaned HTML)', () => {cleaned = 1; run();});
  GM.registerMenuCommand('Copy table (cleaned, links as hrefs)', () => {cleaned = 2; run();});
  GM.registerMenuCommand('Copy table (cleanest, links as hrefs)', () => {cleaned = 3; run();});
})();