Alat pengurutan hololyzer

Tambahkan dukungan untuk mengurutkan daftar super chat hololyzer berdasarkan nama.

// ==UserScript==
// @name hololyzer Sort Tool
// @name:zh hololyzer 排序工具
// @name:zh-TW hololyzer 排序工具
// @name:zh-HK hololyzer 排序工具
// @name:zh-CN hololyzer 排序工具
// @name:zh-SG hololyzer 排序工具
// @name:en hololyzer Sort Tool
// @name:ja hololyzerのソートツール
// @name:ko hololyzer 정렬 도구
// @name:fr Outil de tri hololyzer
// @name:es Herramienta de ordenación hololyzer
// @name:ar أداة ترتيب هولوليزر
// @name:de hololyzer-Sortierwerkzeug
// @name:hi होलोलाइज़र सॉर्ट टूल
// @name:ru Инструмент сортировки hololyzer
// @name:pt Ferramenta de classificação hololyzer
// @name:sw Chombo cha utaratibu wa hololyzer
// @name:sr Alat za sortiranje hololajzera
// @name:hr Alat za sortiranje hololajzera
// @name:it Strumento di ordinamento hololyzer
// @name:ms Alat penyusunan hololyzer
// @name:id Alat pengurutan hololyzer
// @name:nl hololyzer sorteertool
// @name:fa ابزار مرتب سازی hololyzer
// @namespace          https://github.com/kevin823lin
// @version            0.3
// @description Add support for sorting hololyzer's super chat list by name.
// @description:zh 讓 hololyzer 的超級留言清單支援姓名排序功能
// @description:zh-TW 讓 hololyzer 的超級留言清單支援姓名排序功能
// @description:zh-HK 讓 hololyzer 的超級留言清單支援姓名排序功能
// @description:zh-CN 让 hololyzer 的超级留言清单支持姓名排序功能
// @description:zh-SG 让 hololyzer 的超级留言清单支持姓名排序功能
// @description:en Add support for sorting hololyzer's super chat list by name.
// @description:ja hololyzerのスーパーチャットリストを名前でソートする機能を追加する
// @description:ko hololyzer의 슈퍼 챗 목록을 이름별로 정렬하는 기능
// @description:fr Ajouter la prise en charge du tri de la liste de super chats hololyzer par nom.
// @description:es Agregar soporte para ordenar la lista de super chats de hololyzer por nombre.
// @description:ar إضافة دعم لترتيب قائمة الدردشة الخارقة لـ hololyzer حسب الاسم.
// @description:de Unterstützung zum Sortieren der Super-Chat-Liste von hololyzer nach Namen hinzufügen.
// @description:hi नाम द्वारा होलोलाइजर के सुपर चैट सूची को सॉर्ट करने के लिए समर्थन जोड़ें।
// @description:ru Добавить поддержку сортировки списка супер-чатов hololyzer по имени.
// @description:pt Adicionar suporte para classificar a lista de superchats do hololyzer por nome.
// @description:sw Ongeza msaada wa kupanga orodha ya gumzo kuu la hololyzer kwa jina.
// @description:sr Dodajte podršku za sortiranje super-čet liste hololyzer-a po imenu.
// @description:hr Dodajte podršku za sortiranje super chat liste hololyzer po imenu.
// @description:it Aggiungere il supporto per ordinare l'elenco dei super chat di hololyzer per nome.
// @description:ms Tambah sokongan untuk mengurutkan senarai sembang super hololyzer mengikut nama.
// @description:id Tambahkan dukungan untuk mengurutkan daftar super chat hololyzer berdasarkan nama.
// @description:nl Voeg ondersteuning toe voor het sorteren van de superchatlijst van hololyzer op naam.
// @description:fa پشتیبانی از مرتب سازی لیست چت های فوق العاده hololyzer بر اساس نام.
// @author             kevin823lin
// @match              https://www.hololyzer.net/*/superchat/*
// @icon               https://www.google.com/s2/favicons?domain=hololyzer.net
// @grant              none
// @date               2023-03-08
// ==/UserScript==
/*Translate and optimize with ChatGPT*/

(function () {
    'use strict';

    // Your code here...
    init();

    function i18n(name, param) {
        const lang = navigator.appName == "Netscape" ? navigator.language : navigator.userLanguage;
        let config = {};
        switch (lang) {
            case "zh":
            case "zh-TW":
            case "zh-HK":
                config = {
                    sortByTime: "時間排序",
                    sortByName: "姓名排序",
                    copyTable: "複製表格",
                    copyFailed: "複製失敗"
                };
                break;
            case "zh-CN":
            case "zh-SG":
                config = {
                    sortByTime: "时间排序",
                    sortByName: "姓名排序",
                    copyTable: "复制表格",
                    copyFailed: "复制失败"
                };
                break;
            case "en":
                config = {
                    sortByTime: "Sort by time",
                    sortByName: "Sort by name",
                    copyTable: "Copy table",
                    copyFailed: "Copy failed"
                };
                break;
            case "ja":
                config = {
                    sortByTime: "時間で並び替え",
                    sortByName: "名前で並び替え",
                    copyTable: "表をコピーする",
                    copyFailed: "コピーに失敗しました"
                };
                break;

            case "ko":
                config = {
                    sortByTime: "시간순 정렬",
                    sortByName: "이름순 정렬",
                    copyTable: "표 복사하기",
                    copyFailed: "복사 실패"
                };
                break;
            case "fr":
                config = {
                    sortByTime: "Trier par temps",
                    sortByName: "Trier par nom",
                    copyTable: "Copier le tableau",
                    copyFailed: "Copie échouée"
                };
                break;
            case "es":
                config = {
                    sortByTime: "Ordenar por tiempo",
                    sortByName: "Ordenar por nombre",
                    copyTable: "Copiar tabla",
                    copyFailed: "Error al copiar"
                };
                break;
            case "ar":
                config = {
                    sortByTime: "ترتيب حسب الوقت",
                    sortByName: "ترتيب حسب الاسم",
                    copyTable: "نسخ الجدول",
                    copyFailed: "فشل النسخ"
                };
                break;
            case "de":
                config = {
                    sortByTime: "Nach Zeit sortieren",
                    sortByName: "Nach Name sortieren",
                    copyTable: "Tabelle kopieren",
                    copyFailed: "Kopieren fehlgeschlagen"
                };
                break;
            case "hi":
                config = {
                    sortByTime: "समय के अनुसार क्रमबद्ध करें",
                    sortByName: "नाम के अनुसार क्रमबद्ध करें",
                    copyTable: "टेबल कॉपी करें",
                    copyFailed: "कॉपी असफल"
                };
                break;
            case "ru":
                config = {
                    sortByTime: "Сортировать по времени",
                    sortByName: "Сортировать по имени",
                    copyTable: "Копировать таблицу",
                    copyFailed: "Ошибка копирования"
                };
                break;
            case "pt":
                config = {
                    sortByTime: "Ordenar por hora",
                    sortByName: "Ordenar por nome",
                    copyTable: "Copiar tabela",
                    copyFailed: "Falha ao copiar"
                };
                break;
            case "sw":
                config = {
                    sortByTime: "Sort by Time",
                    sortByName: "Sort by Name",
                    copyTable: "Copy Table",
                    copyFailed: "Kushindwa nakala"
                };
                break;
            case "sr":
            case "hr":
            case "it":
                config = {
                    sortByTime: "Sortiraj po vremenu",
                    sortByName: "Sortiraj po imenu",
                    copyTable: "Kopiraj tabelu",
                    copyFailed: "Kopiranje nije uspelo"
                };
                break;
            case "ms":
            case "id":
                config = {
                    sortByTime: "Susun mengikut masa",
                    sortByName: "Susun mengikut nama",
                    copyTable: "Salin Jadual",
                    copyFailed: "Salinan gagal"
                };
                break;
            case "nl":
                config = {
                    sortByTime: "Sorteren op tijd",
                    sortByName: "Sorteren op naam",
                    copyTable: "Tabel kopiëren",
                    copyFailed: "Kopiëren mislukt"
                };
                break;
            case "fa":
                config = {
                    sortByTime: "مرتب سازی بر اساس زمان",
                    sortByName: "مرتب سازی بر اساس نام",
                    copyTable: "رونوشت جدول",
                    copyFailed: "کپی ناموفق"
                };
                break;
            default:
                config = {
                    sortByTime: "Sort by time",
                    sortByName: "Sort by name",
                    copyTable: "Copy table",
                    copyFailed: "Copy failed"
                };
                break;
        }
        return config[name] ? config[name].replace("#t#", param) : name;
    }

    async function init() {
        document.body.dataset.sortBy = "time";
        insertButton();
        await waitElementsLoaded('table[border]');
        const { tbody, newTbody } = getTbodyAndFakeTbody();
        insertNoByBame(newTbody);
        replaceTbody(tbody, newTbody);
    }

    function insertButton() {
        const sortByTimeBtn = document.createElement('button');
        const sortByNameBtn = document.createElement('button');
        const copyTableBtn = document.createElement('button');
        sortByTimeBtn.innerText = i18n('sortByTime');
        sortByNameBtn.innerText = i18n('sortByName');
        copyTableBtn.innerText = i18n('copyTable');
        sortByTimeBtn.addEventListener("click", function () {
            if (document.body.dataset.sortBy !== "time") {
                document.body.dataset.sortBy = "time";
                main("time");
            }
        });
        sortByNameBtn.addEventListener("click", async function () {
            if ((document.body.dataset.sortBy) !== "name") {
                document.body.dataset.sortBy = "name";
                main("name");
            }
        });
        copyTableBtn.addEventListener("click", function () {
            copyTable();
        });
        document.body.insertAdjacentElement('afterbegin', copyTableBtn);
        document.body.insertAdjacentElement('afterbegin', sortByNameBtn);
        document.body.insertAdjacentElement('afterbegin', sortByTimeBtn);
    }

    function insertNoByBame(tbody) {
        const ths = [...tbody.querySelectorAll("th")];
        const trs = [...tbody.querySelectorAll("tr:has(>td):nth-child(odd)")];

        const insertIndex = ((index) => index === -1 ? 0 : index)(ths.findIndex(th => /^(n|N)o$/.test(th.innerText)));
        const sortIndex = ((index) => index === -1 ? 6 : index)(ths.findIndex(th => /name|チャンネル名/.test(th.innerText)));

        const sortedTrs = sortTrs(trs, sortIndex);

        let count = 0;
        const countList = sortedTrs.map((tr, i) => {
            const preName = sortedTrs[i - 1]?.element.children[sortIndex]?.childNodes[0].textContent;
            const name = tr.element.children[sortIndex]?.childNodes[0].textContent;
            return preName !== name ? ++count : count;
        });

        sortedTrs.forEach((tr, i) => {
            const insertEle = tr.element.insertCell(insertIndex + 1);
            insertEle.innerText = countList[i];
            insertEle.setAttribute('rowspan', 2);
            insertEle.style.textAlign = "right";
        });

        const parentRow = tbody.querySelector("tr");
        const childrenEle = parentRow.children[insertIndex + 1];
        const insertEle = document.createElement("th");
        insertEle.innerText = "no by name";
        insertEle.setAttribute('rowspan', 2);
        parentRow.insertBefore(insertEle, childrenEle);
    }

    async function main(sortBy) {
        const { tbody, newTbody } = getTbodyAndFakeTbody();
        sort(newTbody, sortBy);
        replaceTbody(tbody, newTbody);
    }

    function sort(tbody, sortBy) {
        const trs = [...tbody.querySelectorAll("tr:has(>td):nth-child(odd)")];

        let sortIndex;

        switch (sortBy) {
            case "time":
                sortIndex = ((index) => index === -1 ? 0 : index)([...tbody.querySelectorAll('th')].findIndex(th => /^(n|N)o$/.test(th.innerText)));
                break;
            case "name":
                sortIndex = ((index) => index === -1 ? 1 : index)([...tbody.querySelectorAll('th')].findIndex(th => /no by name/.test(th.innerText)));
                break;
        }
        const sortedTrs = sortTrs(trs, sortIndex, true);

        sortedTrs.forEach(item => { tbody.appendChild(item.element); tbody.appendChild(item.nextElementSibling) });
    }

    function sortTrs(trs, sortIndex, num = false) {
        return trs.map(tr => ({
            element: tr,
            previousElementSibling: tr.previousElementSibling,
            nextElementSibling: tr.nextElementSibling,
            key: tr.children[sortIndex].innerText
        })).sort((a, b) => num ? (a.key - b.key) : a.key.localeCompare(b.key, 'ja'));
    }

    function getTbodyAndFakeTbody() {
        const tbody = document.querySelector('table[border] > tbody');

        const clonedTbody = tbody.cloneNode(true);
        const fragment = new DocumentFragment();
        fragment.append(clonedTbody);

        const newTbody = fragment.querySelector('tbody')

        return { tbody, newTbody };
    }

    function replaceTbody(tbody, newTbody) {
        tbody.replaceWith(newTbody);
    }

    function copyTable() {
        copyElement(document.querySelector('table[border] > tbody'));
    }

    function copyElement(ele) {
        if (document.createRange && window.getSelection) {
            const sel = window.getSelection();
            const oldRange = Array.from({ length: sel.rangeCount }, (_, i) => sel.getRangeAt(i));
            const copyRange = document.createRange();
            sel.removeAllRanges();
            try {
                copyRange.selectNode(ele);
                sel.addRange(copyRange);
            } catch (e) {
                copyRange.selectNodeContents(ele);
                sel.addRange(copyRange);
            }
            navigator.clipboard.writeText(sel.toString());
            sel.removeAllRanges();
            oldRange.forEach(range => { sel.addRange(range) });
        } else {
            alert(i18n('copyFailed'));
        }
    }

    function waitElementsLoaded(...eles) {
        return Promise.all(eles.map(ele => {
            return new Promise(async resolve => {
                while (!document.querySelector(ele)) {
                    await wait(100);
                }
                resolve();
            });
        }));
    }

    function wait(ms) {
        try {
            return new Promise(r => setTimeout(r, ms));
        } catch (e) {
            console.error(`wait: ${e}`);
        }
    }
})();