[MWI]Guild Members Table Sorting

Add sorting functionality to the guild members table in Milky Way Idle game

// ==UserScript==
// @name         [MWI]Guild Members Table Sorting
// @name:zh-CN   [银河奶牛]公会成员表格排序
// @namespace    https://cnb.cool/shenhuanjie/skyner-cn/tamper-monkey-script/mwi-guid-members-table-sorting
// @version      1.0.6
// @description  Add sorting functionality to the guild members table in Milky Way Idle game
// @description:zh-CN  为银河放置游戏的公会成员表格添加排序功能
// @author       shenhuanjie
// @license      MIT
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @icon         https://www.milkywayidle.com/favicon.svg
// @grant        none
// @homepage     https://cnb.cool/shenhuanjie/skyner-cn/tamper-monkey-script/mwi-guid-members-table-sorting
// @supportURL   https://cnb.cool/shenhuanjie/skyner-cn/tamper-monkey-script/mwi-guid-members-table-sorting/-/issues
// ==/UserScript==

(function() {
    'use strict';

    // 判断用户语言环境
    const isChinese = navigator.language.includes('zh');

    const messages = {
        zh: {
            scriptStarted: '公会成员表格排序脚本已启动',
            tableNotFound: '未找到公会成员表格,等待用户点击重试',
            tableFound: '已找到公会成员表格,开始绑定排序功能',
            headerClicked: '第 {column} 列表头被点击,当前点击次数: {count}',
            resetSort: '第 {column} 列表头第三次被点击,重置表格排序',
            sortDirectionChanged: '第 {column} 列的排序方向切换为: {direction}',
            sortCompleted: '第 {column} 列排序完成',
            styleAdded: '已添加必要表格样式',
            hideButton: '已隐藏转让会长按钮'
        },
        en: {
            scriptStarted: 'Guild members table sorting script has been started',
            tableNotFound: 'Guild members table not found. Waiting for user click to retry',
            tableFound: 'Guild members table found. Starting to bind sorting function',
            headerClicked: 'The header of column {column} has been clicked. Current click count: {count}',
            resetSort: 'The header of column {column} has been clicked for the third time. Resetting table sorting',
            sortDirectionChanged: 'The sorting direction of column {column} has been changed to: {direction}',
            sortCompleted: 'Sorting of column {column} is completed',
            styleAdded: 'Necessary table styles have been added',
            hideButton: 'The "Transfer Guild Master" button has been hidden'
        }
    };

    function getMessage(key, replacements = {}) {
        const lang = isChinese? 'zh' : 'en';
        let message = messages[lang][key];
        for (const [placeholder, value] of Object.entries(replacements)) {
            message = message.replace(`{${placeholder}}`, value);
        }
        return message;
    }

    console.log(getMessage('scriptStarted'));

    let table = null;
    let hideTransferBtn = null;
    function initTableSort() {
        // 使用属性选择器进行模糊匹配
        table = document.querySelector('[class^="GuildPanel_membersTable__"]');
        if (!table) {
            // console.log(getMessage('tableNotFound'));
            return;
        }
        console.log(getMessage('tableFound'));

        const headers = table.querySelectorAll('th');
        let sortDirections = Array(headers.length).fill(null);
        let clickCounts = Array(headers.length).fill(0);
        const initialRows = Array.from(table.querySelectorAll('tbody tr'));

        headers.forEach((header, index) => {
            header.addEventListener('click', () => {
                clickCounts[index]++;
                // console.log(getMessage('headerClicked', { column: index + 1, count: clickCounts[index] }));

                if (clickCounts[index] === 3) {
                    // console.log(getMessage('resetSort', { column: index + 1 }));
                    const tbody = table.querySelector('tbody');
                    initialRows.forEach(row => tbody.appendChild(row));
                    headers.forEach(h => {
                        h.classList.remove('asc', 'desc');
                    });
                    sortDirections[index] = null;
                    clickCounts[index] = 0;
                    return;
                }

                const rows = Array.from(table.querySelectorAll('tbody tr'));

                if (sortDirections[index] === 'asc') {
                    sortDirections[index] = 'desc';
                } else {
                    sortDirections[index] = 'asc';
                }
                // console.log(getMessage('sortDirectionChanged', { column: index + 1, direction: sortDirections[index] }));

                headers.forEach(h => {
                    h.classList.remove('asc', 'desc');
                });

                if (sortDirections[index] === 'asc') {
                    header.classList.add('asc');
                } else {
                    header.classList.add('desc');
                }

                rows.sort((a, b) => {
                    const keyA = getCellValue(a, index);
                    const keyB = getCellValue(b, index);

                    let comparison;
                    if (typeof keyA === 'number' && typeof keyB === 'number') {
                        comparison = keyA - keyB;
                    } else if (typeof keyA ==='string' && typeof keyB ==='string') {
                        comparison = keyA.localeCompare(keyB);
                    } else if (keyA instanceof SVGElement && keyB instanceof SVGElement) {
                        const useA = keyA.querySelector('use');
                        const useB = keyB.querySelector('use');
                        const hrefA = useA? useA.getAttribute('href') : '';
                        const hrefB = useB? useB.getAttribute('href') : '';
                        comparison = hrefA.localeCompare(hrefB);
                    } else {
                        comparison = 0;
                    }

                    return sortDirections[index] === 'asc'? comparison : -comparison;
                });

                const tbody = table.querySelector('tbody');
                rows.forEach(row => tbody.appendChild(row));
                // console.log(getMessage('sortCompleted', { column: index + 1 }));
            });
        });

        const style = document.createElement('style');
        style.textContent = `
            th {
                cursor: pointer;
            }
            th.asc::after {
                content: ' ↑';
            }
            th.desc::after {
                content: ' ↓';
            }
        `;
        document.head.appendChild(style);
        console.log(getMessage('styleAdded'));
    }

    initTableSort();

    if (!table) {
        document.addEventListener('click', function clickHandler() {
            initTableSort();
            if (table) {
                document.removeEventListener('click', clickHandler);
            }
        });
    }

    if(!hideTransferBtn){
        document.addEventListener('click', function clickHandler() {
            // 隐藏转让会长按钮
            const actionButtonDivs = document.querySelectorAll('[class^="GuildPanel_actionButtons__"]');
            actionButtonDivs.forEach(div => {
                const buttons = div.querySelectorAll('button');
                buttons.forEach(button => {
                    const targetText = isChinese? '转让会长' : 'Transfer Guild Master';
                    if (button.textContent.trim() === targetText) {
                        button.style.display = 'none';
                        console.log(getMessage('hideButton'));
                        hideTransferBtn = true;
                    }
                });
            });
        });
    }

    function getCellValue(row, index) {
        const cell = row.querySelectorAll('td')[index];
        const svg = cell.querySelector('svg');
        if (svg) {
            return svg;
        }
        const text = cell.textContent.trim();
        const num = parseFloat(text);
        return isNaN(num)? text : num;
    }
})();