Torn City Reviver's Tool

A tool to make it easier to revive people while in the Hospital page: Checks if users are available for revive in Torn City while in the hospital page, displays their names with revive buttons, and updates on page change, with collapsible box feature, status filter (Active, Idle, Offline), sorting options, pagination and manual refresh. Now with resizable box!

// ==UserScript==
// @name         Torn City Reviver's Tool
// @namespace    http://tampermonkey.net/
// @version      2.1.1
// @description  A tool to make it easier to revive people while in the Hospital page: Checks if users are available for revive in Torn City while in the hospital page, displays their names with revive buttons, and updates on page change, with collapsible box feature, status filter (Active, Idle, Offline), sorting options, pagination and manual refresh. Now with resizable box!
// @author       LilyWaterbug [2608747]
// @match        https://www.torn.com/hospitalview.php*
// @grant        none
// @license     MIT
// ==/UserScript==

(function() {
    'use strict';

    const style = document.createElement('style');
    style.textContent = `
    /* Ocultar apariencia nativa */
    .torn-filter-checkbox {
        appearance: none;
        -webkit-appearance: none;
        width: 14px;
        height: 14px;
        border: 2px solid currentColor;
        border-radius: 3px;
        margin-right: 6px;
        cursor: pointer;
        transition: background-color 0.2s, transform 0.1s, box-shadow 0.2s;
        /* 💥 Sombra por defecto */
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    }
    /* Cuando está activado, colorear totalmente */
    .torn-filter-checkbox:checked {
        background-color: currentColor;
        box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
    }
    /* Pequeña animación al hacer click */
    .torn-filter-checkbox:active {
        transform: scale(0.9);
    }
    .torn-filter-label {
        display: flex;
        align-items: center;
        font-size: 13px;
        font-weight: bold;
        cursor: pointer;
    }
    `;
    document.head.appendChild(style);

    // Create a collapsible box to display available users
    const resultBox = document.createElement('div');
    resultBox.classList.add('torn-reviver-box');
    // Default initial position on left
    const defaultLeft = '10px';
    const defaultTop = '10px';
    const defaultWidth  = '210px';
    const defaultHeight = '';
    resultBox.style.position = 'fixed';
    resultBox.style.left = defaultLeft;
    resultBox.style.top = defaultTop;
    resultBox.style.width = '210px';
    resultBox.style.maxHeight = '80vh';
    resultBox.style.overflowY = 'auto';
    resultBox.style.padding = '10px';
    resultBox.style.backgroundColor = '#f4f4f4';
    resultBox.style.border = '1px solid #ccc';
    resultBox.style.zIndex = '1000';
    resultBox.style.fontSize = '14px';
    resultBox.style.borderRadius = '8px';
    resultBox.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
    resultBox.style.resize = 'both';
    resultBox.style.overflow = 'auto';

    // Title bar container
      const titleBar = document.createElement('div');
    titleBar.style.display = 'flex';
    titleBar.style.justifyContent = 'space-between';
    titleBar.style.alignItems = 'center';
    titleBar.style.cursor = 'grab';
    titleBar.style.marginBottom = '8px';

    // Title text and counter
    const titleText = document.createElement('strong');
    titleText.innerHTML = 'Available to revive (<span id="user-count">0</span>):';
    titleBar.appendChild(titleText);

    // Controls in title: reset and collapse
    const titleControls = document.createElement('div');
    titleControls.style.display = 'flex';
    titleControls.style.alignItems = 'center';

    // Reset button
    const resetBtn = document.createElement('button');
    resetBtn.textContent = '⟳';
    resetBtn.title = 'Reset position';
    resetBtn.style.marginRight = '4px';
    resetBtn.style.cursor = 'pointer';
    resetBtn.addEventListener('click', () => {
        resultBox.style.left = defaultLeft;
        resultBox.style.top = defaultTop;
        resultBox.style.width  = defaultWidth;
        resultBox.style.height = defaultHeight;
        localStorage.removeItem('tornReviver_posX');
        localStorage.removeItem('tornReviver_posY');
        localStorage.removeItem('tornReviver_width');
        localStorage.removeItem('tornReviver_height');

        // Asegurarnos de que el contenido esté visible
        content.style.display = 'block';
        // Volver el indicador a ▼
        collapseIndicator.textContent = '▼';
        // Quitar el flag de collapsed del localStorage
        localStorage.removeItem('tornReviver_collapsed');
        // (Opcional) Restaurar altura al valor por defecto
        resultBox.style.height = defaultHeight;

    });
    titleControls.appendChild(resetBtn);

    // Add visual indicator for resize in the bottom-right corner
    const resizeIndicator = document.createElement('div');
    resizeIndicator.style.position = 'absolute';
    resizeIndicator.style.right = '2px';
    resizeIndicator.style.bottom = '2px';
    resizeIndicator.style.width = '10px';
    resizeIndicator.style.height = '10px';
    resizeIndicator.style.borderRight = '2px solid #888';
    resizeIndicator.style.borderBottom = '2px solid #888';
    resizeIndicator.style.pointerEvents = 'none'; // Let events pass through
    resultBox.appendChild(resizeIndicator);

    // Collapsible title with user counter
    const title = document.createElement('div');

    // Collapse indicator
    const collapseIndicator = document.createElement('span');
    collapseIndicator.textContent = '▼';
    collapseIndicator.style.cursor = 'pointer';
    collapseIndicator.style.fontSize = '12px';
    titleControls.appendChild(collapseIndicator);

    titleBar.appendChild(titleControls);
    resultBox.appendChild(titleBar);

    title.addEventListener('click', (e) => {
        // Only toggle content if not dragging
        if (!isDragging) {
            content.style.display = content.style.display === 'none' ? 'block' : 'none';
            collapseIndicator.textContent = content.style.display === 'none' ? '▶' : '▼';
            // Save preference
            localStorage.setItem('tornReviver_collapsed', content.style.display === 'none');
        }
    });
    resultBox.appendChild(title);

    // Sort menu with improved style
    const sortMenu = document.createElement('select');
    sortMenu.innerHTML = `
        <option value="status">By Status (Active, Idle, Offline)</option>
        <option value="status-reverse">By Status (Offline, Idle, Active)</option>
        <option value="time">By Time (Longest to Shortest)</option>
        <option value="time-reverse">By Time (Shortest to Longest)</option>
    `;
    sortMenu.style.marginBottom = '10px';
    sortMenu.style.width = '100%';
    sortMenu.style.padding = '5px';
    sortMenu.style.borderRadius = '4px';
    sortMenu.style.border = '1px solid #ccc';
    sortMenu.style.backgroundColor = '#f9f9f9';
    sortMenu.addEventListener('change', () => {
        localStorage.setItem('tornReviver_sortBy', sortMenu.value);
        updateAvailableUsers();
    });
    resultBox.appendChild(sortMenu);

    // Status filter checkboxes
    const filterContainer = document.createElement('div');
    filterContainer.style.display = 'flex';
    filterContainer.style.justifyContent = 'space-between';
    filterContainer.style.marginBottom = '10px';
    filterContainer.style.padding = '5px';
    filterContainer.style.backgroundColor = '#f0f0f0';
    filterContainer.style.borderRadius = '4px';

    const statuses = ['Active', 'Idle', 'Offline'];
    const colors = {
        'Active': '#28a745',
        'Idle': '#ffc107',
        'Offline': '#6c757d'
    };

    statuses.forEach(status => {
    const filterOption = document.createElement('label');
    filterOption.htmlFor = `filter-${status.toLowerCase()}`;
    filterOption.classList.add('torn-filter-label');

    const checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    checkbox.id   = `filter-${status.toLowerCase()}`;
    checkbox.checked = true;
    checkbox.classList.add('torn-filter-checkbox');
    checkbox.style.color = colors[status];
    checkbox.addEventListener('change', updateAvailableUsers);

    // Pintar el label con el color de status
    filterOption.style.color = colors[status];

    // Texto del status
    const textNode = document.createTextNode(status);

    filterOption.appendChild(checkbox);
    filterOption.appendChild(textNode);
    filterContainer.appendChild(filterOption);
    });

    resultBox.appendChild(filterContainer);

    const factionFilterContainer = document.createElement('div');
    factionFilterContainer.style.marginBottom = '10px';
    factionFilterContainer.style.padding = '5px';
    factionFilterContainer.style.backgroundColor = '#f0f0f0';
    factionFilterContainer.style.borderRadius = '4px';
    factionFilterContainer.innerHTML = `
        <div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
            <label class="torn-filter-label" style="color: #666;">
                <input type="checkbox" id="filter-has-faction" class="torn-filter-checkbox" checked style="color: #666;">
                Faction
            </label>
            <label class="torn-filter-label" style="color: #666;">
                <input type="checkbox" id="filter-no-faction" class="torn-filter-checkbox" checked style="color: #666;">
                Factionless
            </label>
        </div>
        <div>
            <input type="text" id="faction-id-filter" placeholder="🔍 Faction ID" style="width: 100%; padding: 3px; border-radius: 3px; border: 1px solid #ccc;">
        </div>
    `;

    resultBox.appendChild(factionFilterContainer);

    // Pagination controls and reload page button
    const controlsContainer = document.createElement('div');
    controlsContainer.style.display = 'flex';
    controlsContainer.style.justifyContent = 'space-between';
    controlsContainer.style.marginBottom = '10px';

    const prevPageButton = document.createElement('button');
    prevPageButton.textContent = '⬅️';
    prevPageButton.style.cursor = 'pointer';
    prevPageButton.style.padding = '5px';
    prevPageButton.style.borderRadius = '4px';
    prevPageButton.style.border = '1px solid #ccc';
    prevPageButton.style.backgroundColor = '#f9f9f9';
    prevPageButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.05)';
    prevPageButton.addEventListener('click', () => navigatePage(-1));
    controlsContainer.appendChild(prevPageButton);

    const reloadButton = document.createElement('button');
    reloadButton.textContent = '🔁';
    reloadButton.style.cursor = 'pointer';
    reloadButton.style.padding = '5px';
    reloadButton.style.borderRadius = '4px';
    reloadButton.style.border = '1px solid #ccc';
    reloadButton.style.backgroundColor = '#f9f9f9';
    reloadButton.title = 'Reload page';
    reloadButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.05)';
    reloadButton.addEventListener('click', () => location.reload());
    controlsContainer.appendChild(reloadButton);

    const nextPageButton = document.createElement('button');
    nextPageButton.textContent = '➡️';
    nextPageButton.style.cursor = 'pointer';
    nextPageButton.style.padding = '5px';
    nextPageButton.style.borderRadius = '4px';
    nextPageButton.style.border = '1px solid #ccc';
    nextPageButton.style.backgroundColor = '#f9f9f9';
    nextPageButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.05)';
    nextPageButton.addEventListener('click', () => navigatePage(1));
    controlsContainer.appendChild(nextPageButton);

    resultBox.appendChild(controlsContainer);

    // Stats bar for quick info
    const statsBar = document.createElement('div');
    statsBar.style.fontSize = '12px';
    statsBar.style.marginBottom = '8px';
    statsBar.style.padding = '5px';
    statsBar.style.borderRadius = '4px';
    statsBar.style.backgroundColor = '#e9e9e9';
    statsBar.style.display = 'flex';
    statsBar.style.justifyContent = 'space-between';
    statsBar.innerHTML = `
        <span id="active-count" style="color:${colors['Active']}">Active: 0</span>
        <span id="idle-count" style="color:${colors['Idle']}">Idle: 0</span>
        <span id="offline-count" style="color:${colors['Offline']}">Offline: 0</span>
    `;
    resultBox.appendChild(statsBar);

    const factionStatsBar = document.createElement('div');
    factionStatsBar.style.fontSize = '12px';
    factionStatsBar.style.marginBottom = '8px';
    factionStatsBar.style.padding = '5px';
    factionStatsBar.style.borderRadius = '4px';
    factionStatsBar.style.backgroundColor = '#e9e9e9';
    factionStatsBar.style.display = 'flex';
    factionStatsBar.style.justifyContent = 'space-between';
    factionStatsBar.innerHTML = `
        <span id="faction-count" style="color:#3A5998">With Faction: 0</span>
        <span id="no-faction-count" style="color:#666">Factionless: 0</span>
        <span id="matching-faction-count" style="color:#28a745"></span>
    `;
    resultBox.appendChild(factionStatsBar);

    // Content that collapses
    const content = document.createElement('div');
    content.style.display = localStorage.getItem('tornReviver_collapsed') === 'true' ? 'none' : 'block';
    if (content.style.display === 'none') {
        collapseIndicator.textContent = '▶';
        // collapse height
        resultBox.style.height = titleBar.offsetHeight + 'px';
    }
    resultBox.appendChild(content);

    // Collapse toggle only on icon click
    let prevHeight = '';
    collapseIndicator.addEventListener('click', (e) => {
        e.stopPropagation();
        const isCollapsed = content.style.display === 'none';
        if (isCollapsed) {
            // Expand
            content.style.display = 'block';
            resultBox.style.height = prevHeight || '';
            collapseIndicator.textContent = '▼';
            localStorage.setItem('tornReviver_collapsed', false);
        } else {
            // Collapse
            prevHeight = resultBox.style.height;
            content.style.display = 'none';
            resultBox.style.height = titleBar.offsetHeight + 'px';
            collapseIndicator.textContent = '▶';
            localStorage.setItem('tornReviver_collapsed', true);
        }

    });

    document.body.appendChild(resultBox);

    const hasFactionFilter = document.getElementById('filter-has-faction');
    const noFactionFilter = document.getElementById('filter-no-faction');
    const factionIdFilter = document.getElementById('faction-id-filter');

    if (hasFactionFilter)   hasFactionFilter .addEventListener('change', updateAvailableUsers);
    if (noFactionFilter)    noFactionFilter  .addEventListener('change', updateAvailableUsers);
    if (factionIdFilter)    factionIdFilter  .addEventListener('input',  updateAvailableUsers);

    // Function to clear result box
    function clearResultBox() {
        while (content.firstChild) {
            content.removeChild(content.firstChild);
        }
    }

    // Function to update the user counter
    function updateUserCount(counts) {
        document.getElementById('user-count').textContent = counts.total;
        document.getElementById('active-count').textContent = `Active: ${counts.active}`;
        document.getElementById('idle-count').textContent = `Idle: ${counts.idle}`;
        document.getElementById('offline-count').textContent = `Offline: ${counts.offline}`;

        // Actualizar contadores de facción
        document.getElementById('faction-count').textContent = `With Faction: ${counts.hasFaction || 0}`;
        document.getElementById('no-faction-count').textContent = `Factionless: ${counts.noFaction || 0}`;

        // Si hay un filtro de facción específico activo, mostrar cuántos coinciden
        const factionFilter = document.getElementById('faction-id-filter')?.value;
        if (factionFilter && counts.matchingFaction !== undefined) {
            document.getElementById('matching-faction-count').textContent =
                `Match "${factionFilter.substring(0, 8)}${factionFilter.length > 8 ? '...' : ''}": ${counts.matchingFaction}`;
            document.getElementById('matching-faction-count').style.display = 'inline';
        } else {
            document.getElementById('matching-faction-count').style.display = 'none';
        }
    }

    // Function to get user status
    function getUserStatus(userElement) {
        const iconTray = userElement.querySelector('#iconTray li');
        if (iconTray && iconTray.title.includes("Online")) {
            return 'Active';
        } else if (iconTray && iconTray.title.includes("Idle")) {
            return 'Idle';
        } else {
            return 'Offline';
        }
    }

    // Function to get user wait time
    function getUserTime(userElement) {
        const timeText = userElement.querySelector('.time')?.innerText || "0m";
        const timeParts = timeText.match(/(\d+)([hm])/);
        if (timeParts) {
            const value = parseInt(timeParts[1]);
            return timeParts[2] === 'h' ? value * 60 : value;
        }
        return 0;
    }

    // Function to add names with links, revive button and status to result box
    function addToResultBox(userData) {
        const userContainer = document.createElement('div');
        userContainer.className = `user-row status-${userData.status.toLowerCase()}`;
        userContainer.style.display = 'grid';
        userContainer.style.gridTemplateColumns = '1fr auto';
        userContainer.style.alignItems = 'center';
        userContainer.style.gap = '8px';
        userContainer.style.marginBottom = '5px';
        userContainer.style.padding = '5px';
        userContainer.style.borderRadius = '4px';
        userContainer.style.backgroundColor = '#fff';
        userContainer.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
        userContainer.style.border = `0px solid ${colors[userData.status]}`;

        const userInfo = document.createElement('div');

        const link = document.createElement('a');
        link.href = `https://www.torn.com/profiles.php?XID=${userData.userId}`;
        link.textContent = `ID: ${userData.userId}`;
        link.target = '_blank';
        link.style.color = '#000';
        link.style.textDecoration = 'none';
        link.style.fontWeight = 'bold';

        const statusIndicator = document.createElement('span');
        statusIndicator.textContent = ` (${userData.status})`;
        statusIndicator.style.color = colors[userData.status];

        const timeIndicator = document.createElement('div');
        timeIndicator.textContent = `⏱️ ${userData.time < 60 ? userData.time + 'm' : Math.floor(userData.time/60) + 'h ' + (userData.time % 60) + 'm'}`;
        timeIndicator.style.fontSize = '11px';
        timeIndicator.style.color = '#666';

        userInfo.appendChild(link);
        userInfo.appendChild(statusIndicator);
        userInfo.appendChild(timeIndicator);

        // Añadir indicador de facción si existe
        if (userData.faction.hasFaction) {
            const factionIndicator = document.createElement('div');
            let factionText = 'Facción: ';
            if (userData.faction.factionName) {
                factionText += userData.faction.factionName;
            } else if (userData.faction.factionId) {
                factionText += `ID: ${userData.faction.factionId}`;
            } else {
                factionText += "Sí";
            }
            factionIndicator.textContent = factionText;
            factionIndicator.style.fontSize = '11px';
            factionIndicator.style.color = '#666';
            userInfo.appendChild(factionIndicator);
        }

        const reviveButton = document.createElement('button');
        reviveButton.style.cursor = 'pointer';
        reviveButton.style.backgroundColor = '#FF6347';
        reviveButton.style.border = 'none';
        reviveButton.style.borderRadius = '50%';
        reviveButton.style.width = '24px';
        reviveButton.style.height = '24px';
        reviveButton.style.display = 'flex';
        reviveButton.style.alignItems = 'center';
        reviveButton.style.justifyContent = 'center';
        reviveButton.innerHTML = '<span style="color:white;font-weight:bold;">+</span>';
        reviveButton.title = 'Click to revive';

        reviveButton.addEventListener('click', function() {
            userData.reviveLinkElement.querySelector('.revive-icon').click();
            reviveButton.disabled = true;
            reviveButton.style.backgroundColor = '#ccc';
            reviveButton.innerHTML = '<span style="color:white;font-weight:bold;">✓</span>';

            setTimeout(() => {
                userData.reviveLinkElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
                // Remove this user from the list or mark as revived after a delay
                setTimeout(() => {
                    userContainer.style.opacity = '0.5';
                    userContainer.style.border = '1px dashed #ccc';
                }, 1000);
            }, 500);
        });

        userContainer.appendChild(userInfo);
        userContainer.appendChild(reviveButton);
        content.appendChild(userContainer);
    }

    // Function to navigate between hospital pages
    let currentPage = 0;
    function navigatePage(direction) {
        currentPage += direction;
        if (currentPage < 0) currentPage = 0; // Don't go back beyond first page
        window.location.href = `https://www.torn.com/hospitalview.php#start=${currentPage * 50}`;
        setTimeout(updateAvailableUsers, 500); // Delay to reload list after page change
    }

    // Función para obtener información de facción
    function getUserFaction(userElement) {
        const factionLink = userElement.querySelector('a.user.faction');
        if (!factionLink) return { hasFaction: false };

        // Verificar si tiene una facción (diferente de solo la clase "user faction")
        const hasFaction = factionLink.classList.length > 2 || factionLink.getAttribute('href');

        // Obtener el ID de facción si está disponible
        let factionId = '';
        let factionName = '';

        if (hasFaction) {
            const href = factionLink.getAttribute('href');
            if (href) {
                const factionIdMatch = href.match(/ID=(\d+)/);
                if (factionIdMatch && factionIdMatch[1]) {
                    factionId = factionIdMatch[1];
                }
            }

            // Intentar obtener el nombre de la facción desde el título de la imagen
            const factionImg = factionLink.querySelector('img');
            if (factionImg && factionImg.title && factionImg.title.trim() !== '') {
                factionName = factionImg.title;
            }
        }

        return {
            hasFaction,
            factionId,
            factionName,
            factionElement: factionLink
        };
    }

    // Function to process and update available users list
    function updateAvailableUsers() {
        clearResultBox();

        // Get active filters
        const activeFilters = {};
        statuses.forEach(status => {
            activeFilters[status] = document.getElementById(`filter-${status.toLowerCase()}`).checked;
        });

        const userContainers = [...document.querySelectorAll('.user-info-list-wrap li')];
        let usersData = userContainers.map(user => {
            const reviveLink = user.querySelector('a.revive');
            if (reviveLink && !reviveLink.classList.contains('reviveNotAvailable')) {
                const href = reviveLink.getAttribute('href');
                const userIdMatch = href.match(/ID=(\d+)/);
                if (userIdMatch && userIdMatch[1]) {
                    return {
                        userId: userIdMatch[1],
                        reviveLinkElement: reviveLink,
                        status: getUserStatus(user),
                        time: getUserTime(user),
                        faction: getUserFaction(user) // Añade esta línea
                    };
                }
            }
            return null;
        }).filter(user => user !== null);

        // Apply status filters
        usersData = usersData.filter(user => activeFilters[user.status]);

        // Apply faction filters
        const showHasFaction = document.getElementById('filter-has-faction').checked;
        const showNoFaction = document.getElementById('filter-no-faction').checked;
        const factionFilter = document.getElementById('faction-id-filter').value.toLowerCase();

        usersData = usersData.filter(user => {
            // Filtro básico de facción
            if (user.faction.hasFaction && !showHasFaction) return false;
            if (!user.faction.hasFaction && !showNoFaction) return false;

            // Filtro por ID o nombre de facción si se ha especificado
            if (factionFilter && user.faction.hasFaction) {
                const matchesId = user.faction.factionId && user.faction.factionId.includes(factionFilter);
                const matchesName = user.faction.factionName &&
                                   user.faction.factionName.toLowerCase().includes(factionFilter);
                if (!matchesId && !matchesName) return false;
            }

            return true;
        });

        // Apply sorting
        const sortBy = sortMenu.value;
        if (sortBy === 'status') {
            usersData.sort((a, b) => {
                const statusOrder = { 'Active': 1, 'Idle': 2, 'Offline': 3 };
                return statusOrder[a.status] - statusOrder[b.status];
            });
        } else if (sortBy === 'status-reverse') {
            usersData.sort((a, b) => {
                const statusOrder = { 'Active': 3, 'Idle': 2, 'Offline': 1 };
                return statusOrder[a.status] - statusOrder[b.status];
            });
        } else if (sortBy === 'time') {
            usersData.sort((a, b) => b.time - a.time);
        } else if (sortBy === 'time-reverse') {
            usersData.sort((a, b) => a.time - b.time);
        }

        // Count users by status
        const counts = {
            active: usersData.filter(u => u.status === 'Active').length,
            idle: usersData.filter(u => u.status === 'Idle').length,
            offline: usersData.filter(u => u.status === 'Offline').length,
            total: usersData.length,
            hasFaction: usersData.filter(u => u.faction.hasFaction).length,
            noFaction: usersData.filter(u => !u.faction.hasFaction).length
        };

        updateUserCount(counts);

        // Add users to the display
        usersData.forEach(userData => {
            addToResultBox(userData);
        });

        if (usersData.length === 0) {
            const noUserMessage = document.createElement('div');
            noUserMessage.textContent = "No users available to revive with current filters.";
            noUserMessage.style.color = 'gray';
            noUserMessage.style.textAlign = 'center';
            noUserMessage.style.fontSize = '12px';
            noUserMessage.style.padding = '10px';
            content.appendChild(noUserMessage);
        }
    }

    // Load saved preferences
    function loadSavedPreferences() {
        // Load sort preference
        const savedSort = localStorage.getItem('tornReviver_sortBy');
        if (savedSort) {
            sortMenu.value = savedSort;
        }

        // Load filter preferences
        statuses.forEach(status => {
            const savedFilter = localStorage.getItem(`tornReviver_filter_${status.toLowerCase()}`);
            if (savedFilter !== null) {
                document.getElementById(`filter-${status.toLowerCase()}`).checked = savedFilter === 'true';
            }
        });

        // Save filter changes
        statuses.forEach(status => {
            document.getElementById(`filter-${status.toLowerCase()}`).addEventListener('change', function() {
                localStorage.setItem(`tornReviver_filter_${status.toLowerCase()}`, this.checked);
            });
        });

        // Load saved size
        const savedWidth = localStorage.getItem('tornReviver_width');
        const savedHeight = localStorage.getItem('tornReviver_height');
        if (savedWidth) resultBox.style.width = savedWidth;
        if (savedHeight) resultBox.style.height = savedHeight;

        const savedHasFaction = localStorage.getItem('tornReviver_filter_hasFaction');
        const savedNoFaction = localStorage.getItem('tornReviver_filter_noFaction');
        if (savedHasFaction !== null) {
            document.getElementById('filter-has-faction').checked = savedHasFaction === 'true';
        }
        if (savedNoFaction !== null) {
            document.getElementById('filter-no-faction').checked = savedNoFaction === 'true';
        }
        const savedFactionFilter = localStorage.getItem('tornReviver_factionFilter');
        if (savedFactionFilter) {
            document.getElementById('faction-id-filter').value = savedFactionFilter;
        }

        // Save faction filter changes
        document.getElementById('filter-has-faction').addEventListener('change', function() {
            localStorage.setItem('tornReviver_filter_hasFaction', this.checked);
        });
        document.getElementById('filter-no-faction').addEventListener('change', function() {
            localStorage.setItem('tornReviver_filter_noFaction', this.checked);
        });
        document.getElementById('faction-id-filter').addEventListener('input', function() {
            localStorage.setItem('tornReviver_factionFilter', this.value);
        });

    }

    // Make the box draggable
    let isDragging = false;
    let dragOffsetX = 0;
    let dragOffsetY = 0;


    titleBar.addEventListener('mousedown', (e) => {
        isDragging = true;
        dragOffsetX = e.clientX - resultBox.getBoundingClientRect().left;
        dragOffsetY = e.clientY - resultBox.getBoundingClientRect().top;
        titleBar.style.cursor = 'grabbing';
        e.preventDefault();
    });

    document.addEventListener('mousemove', (e) => {
        if (!isDragging) return;
        let newLeft = e.clientX - dragOffsetX;
        let newTop = e.clientY - dragOffsetY;
        const maxX = window.innerWidth - resultBox.offsetWidth;
        const maxY = window.innerHeight - resultBox.offsetHeight;
        newLeft = Math.min(Math.max(0, newLeft), maxX);
        newTop = Math.min(Math.max(0, newTop), maxY);
        resultBox.style.left = newLeft + 'px';
        resultBox.style.top = newTop + 'px';
    });

    document.addEventListener('mouseup', () => {
        if (!isDragging) return;
        isDragging = false;
        titleBar.style.cursor = 'grab';
        localStorage.setItem('tornReviver_posX', resultBox.style.left);
        localStorage.setItem('tornReviver_posY', resultBox.style.top);
    });

    // Save resized dimensions
    window.addEventListener('resize', function() {
        if (resultBox.style.width && resultBox.style.height) {
            localStorage.setItem('tornReviver_width', resultBox.style.width);
            localStorage.setItem('tornReviver_height', resultBox.style.height);
        }
    });

    // Specific handler for the result box being resized
    const resizeObserver = new ResizeObserver(entries => {
        for (let entry of entries) {
            if (entry.target.style.width && entry.target.style.height) {
                localStorage.setItem('tornReviver_width', entry.target.style.width);
                localStorage.setItem('tornReviver_height', entry.target.style.height);
            }
        }
    });
    resizeObserver.observe(resultBox);

    // Restore saved position
    const savedX = localStorage.getItem('tornReviver_posX');
    const savedY = localStorage.getItem('tornReviver_posY');
    if (savedX && savedY) {
        resultBox.style.left = savedX;
        resultBox.style.top = savedY;
    }

    // Watch for URL changes to update users
    let lastUrl = window.location.href;
    new MutationObserver(() => {
        const currentUrl = window.location.href;
        if (currentUrl !== lastUrl) {
            lastUrl = currentUrl;
            setTimeout(updateAvailableUsers, 500);
        }
    }).observe(document, { subtree: true, childList: true });

    // Watch for hospital list changes
    const hospitalList = document.querySelector('.user-info-list-wrap');
    if (hospitalList) {
        new MutationObserver(updateAvailableUsers).observe(hospitalList, { childList: true, subtree: true });
    }

    // Initialize
    loadSavedPreferences();
    window.addEventListener('load', updateAvailableUsers);

    // First update after a small delay to ensure DOM is ready
    setTimeout(updateAvailableUsers, 500);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址