Greasy Fork 支持简体中文。

Warhammer Kill Team Rules Sorter

Fan-made sorting tool for Kill Team rules page. Adds alphabetical and date sorting functionality with configurable date formats. This is an unofficial tool not affiliated with, endorsed, or sponsored by Games Workshop. Warhammer Kill Team is a trademark of Games Workshop Limited.

// ==UserScript==
// @name Warhammer Kill Team Rules Sorter
// @namespace https://greasyfork.org/en/users/Kardiff
// @version 1.1.1
// @description Fan-made sorting tool for Kill Team rules page. Adds alphabetical and date sorting functionality with configurable date formats. This is an unofficial tool not affiliated with, endorsed, or sponsored by Games Workshop. Warhammer Kill Team is a trademark of Games Workshop Limited.
// @author Kardiff
// @match https://www.warhammer-community.com/*/downloads/kill-team/
// @license MIT
// @run-at document-idle
// @supportURL https://github.com/Kardiff-Kill-Team/page_sorter/issues
// @homepageURL https://github.com/Kardiff-Kill-Team/page_sorter
// ==/UserScript==

(function() {
    'use strict';

    const styles = `
        .sort-controls {
            position: sticky;
            top: 0;
            background: white;
            padding: 10px;
            z-index: 1000;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
            margin-bottom: 20px;
            display: flex;
            justify-content: center;
            gap: 10px;
        }
        .sort-button {
            padding: 8px 15px;
            border: none;
            border-radius: 4px;
            background: #234;
            color: white;
            cursor: pointer;
            font-weight: bold;
            min-width: 150px;
        }
        .sort-button:hover {
            background: #345;
        }
    `;

    // Date format configurations
    const dateFormats = {
        US: {
            label: 'US (MM/DD/YYYY)',
            format: (day, month, year) => `${month}/${day}/${year}`,
            parse: (text) => {
                const [month, day, year] = text.split('/');
                return new Date(`${year}-${month}-${day}`);
            }
        },
        EU: {
            label: 'EU (DD/MM/YYYY)',
            format: (day, month, year) => `${day}/${month}/${year}`,
            parse: (text) => {
                const [day, month, year] = text.split('/');
                return new Date(`${year}-${month}-${day}`);
            }
        },
        ISO: {
            label: 'ISO (YYYY-MM-DD)',
            format: (day, month, year) => `${year}-${month}-${day}`,
            parse: (text) => new Date(text)
        }
    };

    // Add styles
    const styleSheet = document.createElement("style");
    styleSheet.textContent = styles;
    document.head.appendChild(styleSheet);

    function initializeSorter(container) {
        // Create control buttons
        const controlsDiv = document.createElement('div');
        controlsDiv.className = 'sort-controls';
        
        const alphabeticalButton = document.createElement('button');
        alphabeticalButton.textContent = 'Sort A-Z';
        alphabeticalButton.className = 'sort-button';
        
        const dateButton = document.createElement('button');
        dateButton.textContent = 'Sort by Latest Update';
        dateButton.className = 'sort-button';

        const formatButton = document.createElement('button');
        formatButton.className = 'sort-button';
        let currentFormat = 'US'; // Default format

        // Store original dates to prevent conversion errors
        const originalDates = new Map();

        function updateFormatButtonText() {
            formatButton.textContent = `Format: ${dateFormats[currentFormat].label}`;
        }
        updateFormatButtonText();

        controlsDiv.appendChild(alphabeticalButton);
        controlsDiv.appendChild(dateButton);
        controlsDiv.appendChild(formatButton);
        container.parentElement.insertBefore(controlsDiv, container);

        // Store original dates when first encountering them
        container.querySelectorAll('.shared-downloadCard').forEach(card => {
            const dateSpan = card.querySelector('.border-t span.ml-5');
            if (dateSpan && !originalDates.has(card)) {
                const dateParts = dateSpan.textContent.trim().split('/');
                // Store as day, month, year
                originalDates.set(card, {
                    day: dateParts[0],
                    month: dateParts[1],
                    year: dateParts[2]
                });
            }
        });

        function updateAllDates() {
            container.querySelectorAll('.shared-downloadCard').forEach(card => {
                const dateSpan = card.querySelector('.border-t span.ml-5');
                if (dateSpan && originalDates.has(card)) {
                    const { day, month, year } = originalDates.get(card);
                    if (currentFormat === 'ISO') {
                        dateSpan.textContent = dateFormats.ISO.format(day, month, year);
                    } else if (currentFormat === 'US') {
                        dateSpan.textContent = dateFormats.US.format(day, month, year);
                    } else {
                        dateSpan.textContent = dateFormats.EU.format(day, month, year);
                    }
                }
            });
        }

        // Initial date conversion
        updateAllDates();

        function getTeamBoxes() {
            return Array.from(container.querySelectorAll('.shared-downloadCard'));
        }

        function getTeamName(box) {
            const link = box.querySelector('a[download]');
            return link ? link.textContent.trim() : '';
        }

        function getLastUpdated(box) {
            if (originalDates.has(box)) {
                const { day, month, year } = originalDates.get(box);
                // Always use ISO format for date comparison
                return new Date(`${year}-${month}-${day}`);
            }
            return new Date(0);
        }

        function sortBoxes(sortFunction) {
            const boxes = getTeamBoxes();
            if (boxes.length === 0) return;

            const sortedBoxes = [...boxes].sort(sortFunction);
            sortedBoxes.forEach(box => container.appendChild(box));
        }

        function sortAlphabetically() {
            sortBoxes((a, b) => getTeamName(a).localeCompare(getTeamName(b)));
        }

        function sortByDate() {
            sortBoxes((a, b) => {
                const dateCompare = getLastUpdated(b) - getLastUpdated(a);
                return dateCompare === 0 ? getTeamName(a).localeCompare(getTeamName(b)) : dateCompare;
            });
        }

        alphabeticalButton.addEventListener('click', sortAlphabetically);
        dateButton.addEventListener('click', sortByDate);
        formatButton.addEventListener('click', () => {
            // Cycle through formats: US -> EU -> ISO -> US
            const formats = Object.keys(dateFormats);
            const currentIndex = formats.indexOf(currentFormat);
            currentFormat = formats[(currentIndex + 1) % formats.length];
            updateFormatButtonText();
            updateAllDates();
        });

        // Initial sort
        setTimeout(sortAlphabetically, 1000);
    }

    // Watch for content to load
    const observer = new MutationObserver((mutations, obs) => {
        const downloadCards = document.querySelectorAll('.shared-downloadCard');
        if (downloadCards.length > 0) {
            const container = downloadCards[0].parentElement;
            if (container && !document.querySelector('.sort-controls')) {
                obs.disconnect();
                initializeSorter(container);
            }
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // Fallback timeout
    setTimeout(() => {
        observer.disconnect();
        const downloadCards = document.querySelectorAll('.shared-downloadCard');
        if (downloadCards.length > 0) {
            const container = downloadCards[0].parentElement;
            if (container && !document.querySelector('.sort-controls')) {
                initializeSorter(container);
            }
        }
    }, 10000);
})();