您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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); })();