Tableの表をソート

見出し列をAlt+左クリック/Alt+右クリック:並べ替え

当前为 2024-02-20 提交的版本,查看 最新版本

// ==UserScript==
// @name Tableの表をソート
// @description 見出し列をAlt+左クリック/Alt+右クリック:並べ替え
// @match *://*/*
// @version 0.1.1
// @run-at document-idle
// @namespace https://greasyfork.org/users/181558
// ==/UserScript==

(function() {

  const SORT_ASCENDING_ACTION = "Alt+0"; // Shift+ Alt+ Ctrl+ 0:左ボタン 1:中ボタン 2:右ボタン 3:X1ボタン 4:X2ボタン
  const SORT_DESCENDING_ACTION = "Alt+2";

  document.body.addEventListener("mousedown", e => {
    let key = (e?.shiftKey ? "Shift+" : "") + (e?.altKey ? "Alt+" : "") + (e?.ctrlKey ? "Ctrl+" : "") + e?.button;
    if (key == SORT_ASCENDING_ACTION || key == SORT_DESCENDING_ACTION) {
      //if (e?.target?.matches('table th,table thead td,table>tbody>tr:first-child>td')) {
      if (e?.target?.matches('table th,table td')) {
        let th = e?.target //?.closest('table th,table thead td,table tr:first-child td')
        let table = e?.target?.closest("table");

        if (!table.querySelector('[rowspan],[colspan]')) {
          let body = table?.querySelector('tbody')
          let column = th?.cellIndex;

          [...table.querySelectorAll('td')].forEach(e => e.asnum = parseFloat(e?.textContent?.trim()?.replace(/(\d)\,(\d)/gm, "$1$2"))); // ^1,000$などの数値に関しては,を除去
          [...table.querySelectorAll('td')].forEach(e => { // セル内文字列が「abc100」「100」「100abc」のどれかなら数値が主体のデータだろうと判断し数値データとして見る。「abc100abc」なら文字列と判断。,は無視。
            if (e?.textContent?.trim()?.match(/^[0-9\,\.\–\-]+\D+$|^[0-9\,\.\-\–]+$|^\D+[0-9\,\.\-\–]+$/gm)) {
              e.asnum = parseFloat(e?.textContent?.trim()?.replace(/–/gm, "-")?.replace(/^[^0-9\.\,\-\–]*([0-9\,\.\-\–])/, "$1")?.replace(/(\d)\,(\d)/g, "$1$2")) // なぜか「–」ダッシュをマイナスとして使うサイトもあるのでマイナスに置き換える
            }
          })
          let startrow = Array.from(table.rows).findIndex(r => r.contains(e.target)); //alert(startrow)

          let rows = Array.from(table.rows).slice(0, startrow).concat(
            key == SORT_ASCENDING_ACTION ?
            Array.from(table.rows).slice(startrow).sort((a, b) => a?.cells?.[column]?.asnum && b?.cells?.[column]?.asnum ? a?.cells?.[column]?.asnum == b?.cells?.[column]?.asnum ? 0 : a?.cells?.[column]?.asnum - b?.cells?.[column]?.asnum : new Intl.Collator("ja", { numeric: true, sensitivity: 'base' }).compare(a?.cells?.[column]?.textContent?.trim(), b?.cells?.[column]?.textContent?.trim())) :
            Array.from(table.rows).slice(startrow).sort((a, b) => a?.cells?.[column]?.asnum && b?.cells?.[column]?.asnum ? a?.cells?.[column]?.asnum == b?.cells?.[column]?.asnum ? 0 : b?.cells?.[column]?.asnum - a?.cells?.[column]?.asnum : new Intl.Collator("ja", { numeric: true, sensitivity: 'base' }).compare(b?.cells?.[column]?.textContent?.trim(), a?.cells?.[column]?.textContent?.trim()))
          )
          let fragment = new DocumentFragment(); // prependを使っても順番を維持するためにDocumentFragmentを使う
          rows.forEach(tr => fragment.appendChild(tr))
          body.prepend(fragment)
        } else {
          [...table.querySelectorAll('[colspan]')].forEach(e => {
            let c = e.getAttribute("colspan");
            e.removeAttribute("colspan");
            e.insertAdjacentHTML("afterend", "<td></td>".repeat(c - 1))
          });
          [...table.querySelectorAll('[rowspan]')].forEach(e => { e.removeAttribute("rowspan"); });
          [...table.querySelectorAll('tr')].filter(e => !e.hasChildNodes()).forEach(e => e.remove());
        }
      }
    }
  })

  function sani(s) { return s?.replace(/&/g, "&amp;")?.replace(/"/g, "&quot;")?.replace(/'/g, "&#39;")?.replace(/`/g, '&#x60;')?.replace(/</g, "&lt;")?.replace(/>/g, "&gt;") || "" }
})();