Add shared-ip-manager.userscript.js
# Shared IP Manager - Changelog ## Version 1.1.0 - Feature Enhancement Release ### �� New Features #### Advanced Search & Filtering - **Real-time search** across all table columns (username, country, provider, type, etc.) - **Banned user filter toggle** - quickly view only banned users - **Combined filtering** - search within filtered results - **User count display** with banned user statistics #### Column Sorting - **Sortable Username column** - alphabetical A-Z / Z-A - **Sortable Type column** - group similar types together - **Visual sort indicators** - ↕ (unsorted), ↑ (ascending), ↓ (descending) - **Click to toggle** between ascending/descending order #### Banned User Detection & Styling - **Automatic detection** of banned users from data structure - **Visual indicators**: - 🚫 Ban icon before username - Red strikethrough text - "BANNED" badge with red styling - **Maintains functionality** - banned user links still work #### Enhanced UI/UX - **Dark theme styling** matching overlay design - **Responsive table** with horizontal/vertical scrolling - **Hover effects** on sortable headers and buttons - **Professional layout** with proper spacing and typography ### 🔧 Technical Improvements #### Performance - **Efficient DOM manipulation** - direct element creation vs innerHTML - **Optimized filtering** - real-time updates without lag - **Memory management** - proper event listener cleanup #### Code Quality - **Error handling** - null checks and graceful fallbacks - **Debug logging** - comprehensive console output for troubleshooting - **Modular functions** - clean, maintainable code structure ### 🐛 Bug Fixes - **Fixed null reference errors** in event listener setup - **Resolved sorting issues** with proper DOM element access - **Fixed table overflow** - proper scrolling implementation ### 📊 User Interface #### Layout - **Flexbox search bar** with filter button and count display - **Sticky table headers** that remain visible while scrolling - **Consistent spacing** and visual hierarchy #### Visual Design - **Color scheme**: Dark backgrounds (#101113, #131313) with red accents (#dc2626) - **Typography**: System font stack for better readability - **Interactive elements**: Smooth transitions and hover states ### 🎯 Use Cases #### For Moderators - **Quick banned user review** - filter to see only banned users - **Search specific users** - find users by name, country, or provider - **Sort by activity** - organize by username or connection type #### For Analysis - **Data visualization** - clean table format vs overlay - **Export-friendly** - structured data for further analysis - **Real-time filtering** - dynamic data exploration --- ## Version 1.0.0 - Initial Release ### Core Functionality - Transform shared IP overlay into searchable table format - Basic table structure with user data extraction - Initial dark theme implementation --- *This userscript enhances the shared IP management experience by providing powerful filtering, sorting, and visualization tools for better user moderation and data analysis.*
This commit is contained in:
parent
71c588ee01
commit
2733e86c8f
517
shared-ip-manager.userscript.js
Normal file
517
shared-ip-manager.userscript.js
Normal file
@ -0,0 +1,517 @@
|
||||
// ==UserScript==
|
||||
// @name Shared IP Manager
|
||||
// @namespace http://tampermonkey.net/
|
||||
// @version 1.1.0
|
||||
// @description Transform shared IP overlay into searchable table format
|
||||
// @author Ryahn
|
||||
// @match *://*/*
|
||||
// @grant none
|
||||
// ==/UserScript==
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Function to extract country code from flag class
|
||||
function getCountryCode(flagElement) {
|
||||
const classes = flagElement.className;
|
||||
const match = classes.match(/tck-provider-country-flag\s+(\w+)/);
|
||||
return match ? match[1].toUpperCase() : '';
|
||||
}
|
||||
|
||||
// Function to get country name from flag element
|
||||
function getCountryName(flagElement) {
|
||||
return flagElement.getAttribute('data-original-title') || '';
|
||||
}
|
||||
|
||||
// Function to get provider info
|
||||
function getProviderInfo(providerElement) {
|
||||
return {
|
||||
name: providerElement.textContent.trim(),
|
||||
asn: providerElement.getAttribute('data-original-title') || ''
|
||||
};
|
||||
}
|
||||
|
||||
// Function to get type info
|
||||
function getTypeInfo(typeElement) {
|
||||
return typeElement.getAttribute('data-original-title') || '';
|
||||
}
|
||||
|
||||
// Function to extract usage count
|
||||
function getUsageCount(usageText) {
|
||||
const match = usageText.match(/(\d+)\s+time/);
|
||||
return match ? parseInt(match[1]) : 0;
|
||||
}
|
||||
|
||||
// Function to create flag emoji from country code
|
||||
function getFlagEmoji(countryCode) {
|
||||
if (!countryCode) return '';
|
||||
const codePoints = countryCode
|
||||
.toLowerCase()
|
||||
.split('')
|
||||
.map(char => 127397 + char.charCodeAt());
|
||||
return String.fromCodePoint(...codePoints);
|
||||
}
|
||||
|
||||
// Function to create the table
|
||||
function createTable() {
|
||||
const overlay = document.querySelector('.overlay-content');
|
||||
if (!overlay) return;
|
||||
|
||||
// Find all user entries
|
||||
const userEntries = overlay.querySelectorAll('.block-row.block-row--separated');
|
||||
|
||||
if (userEntries.length === 0) return;
|
||||
|
||||
// Create table container
|
||||
const tableContainer = document.createElement('div');
|
||||
tableContainer.style.cssText = `
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
// Create search and filter container
|
||||
const searchFilterContainer = document.createElement('div');
|
||||
searchFilterContainer.style.cssText = `
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
// Create search input
|
||||
const searchInput = document.createElement('input');
|
||||
searchInput.type = 'text';
|
||||
searchInput.placeholder = 'Search users, countries, providers...';
|
||||
searchInput.style.cssText = `
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
border: 1px solid #101113;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
background: #101113;
|
||||
color: #374151;
|
||||
`;
|
||||
|
||||
// Create banned filter button
|
||||
const bannedFilterButton = document.createElement('button');
|
||||
bannedFilterButton.textContent = 'Show Banned Only';
|
||||
bannedFilterButton.style.cssText = `
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #dc2626;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
background: #dc262620;
|
||||
color: #dc2626;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
// Add hover effect for button
|
||||
bannedFilterButton.addEventListener('mouseenter', function() {
|
||||
this.style.backgroundColor = '#dc2626';
|
||||
this.style.color = 'white';
|
||||
});
|
||||
bannedFilterButton.addEventListener('mouseleave', function() {
|
||||
if (!this.classList.contains('active')) {
|
||||
this.style.backgroundColor = '#dc262620';
|
||||
this.style.color = '#dc2626';
|
||||
}
|
||||
});
|
||||
|
||||
// Create count display
|
||||
const countDisplay = document.createElement('div');
|
||||
countDisplay.style.cssText = `
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
margin-left: auto;
|
||||
padding: 8px 12px;
|
||||
background: #1a1a1a;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #17191b;
|
||||
`;
|
||||
|
||||
// Add button to container
|
||||
searchFilterContainer.appendChild(searchInput);
|
||||
searchFilterContainer.appendChild(bannedFilterButton);
|
||||
searchFilterContainer.appendChild(countDisplay);
|
||||
|
||||
// Create table wrapper for scrolling
|
||||
const tableWrapper = document.createElement('div');
|
||||
tableWrapper.style.cssText = `
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
max-height: calc(100vh - 150px);
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
`;
|
||||
|
||||
// Create table
|
||||
const table = document.createElement('table');
|
||||
table.style.cssText = `
|
||||
width: 100%;
|
||||
min-width: 800px;
|
||||
border-collapse: collapse;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
// Create table header
|
||||
const thead = document.createElement('thead');
|
||||
thead.style.cssText = `position: sticky; top: 0; z-index: 10;`;
|
||||
|
||||
const headerRow = document.createElement('tr');
|
||||
headerRow.style.cssText = `background: #101113; border-bottom: 1px solid #101113; color: #959595;`;
|
||||
|
||||
// Username header (sortable)
|
||||
const usernameHeader = document.createElement('th');
|
||||
usernameHeader.id = 'sort-username';
|
||||
usernameHeader.style.cssText = `padding: 10px 12px; text-align: left; border-right: 1px solid #17191b; font-weight: 600; color: #959595; white-space: nowrap; cursor: pointer; user-select: none;`;
|
||||
usernameHeader.innerHTML = `Username <span id="username-sort-icon" style="margin-left: 5px;">↕</span>`;
|
||||
|
||||
// Join Date header
|
||||
const joinDateHeader = document.createElement('th');
|
||||
joinDateHeader.style.cssText = `padding: 10px 12px; text-align: left; border-right: 1px solid #17191b; font-weight: 600; color: #959595; white-space: nowrap;`;
|
||||
joinDateHeader.textContent = 'Join Date';
|
||||
|
||||
// Messages header
|
||||
const messagesHeader = document.createElement('th');
|
||||
messagesHeader.style.cssText = `padding: 10px 12px; text-align: left; border-right: 1px solid #17191b; font-weight: 600; color: #959595; white-space: nowrap;`;
|
||||
messagesHeader.textContent = 'Messages';
|
||||
|
||||
// Country header
|
||||
const countryHeader = document.createElement('th');
|
||||
countryHeader.style.cssText = `padding: 10px 12px; text-align: left; border-right: 1px solid #17191b; font-weight: 600; color: #959595; white-space: nowrap;`;
|
||||
countryHeader.textContent = 'Country';
|
||||
|
||||
// Provider header
|
||||
const providerHeader = document.createElement('th');
|
||||
providerHeader.style.cssText = `padding: 10px 12px; text-align: left; border-right: 1px solid #17191b; font-weight: 600; color: #959595; white-space: nowrap;`;
|
||||
providerHeader.textContent = 'Provider';
|
||||
|
||||
// Type header (sortable)
|
||||
const typeHeader = document.createElement('th');
|
||||
typeHeader.id = 'sort-type';
|
||||
typeHeader.style.cssText = `padding: 10px 12px; text-align: left; border-right: 1px solid #17191b; font-weight: 600; color: #959595; white-space: nowrap; cursor: pointer; user-select: none;`;
|
||||
typeHeader.innerHTML = `Type <span id="type-sort-icon" style="margin-left: 5px;">↕</span>`;
|
||||
|
||||
// Usage header
|
||||
const usageHeader = document.createElement('th');
|
||||
usageHeader.style.cssText = `padding: 10px 12px; text-align: left; font-weight: 600; color: #959595; white-space: nowrap;`;
|
||||
usageHeader.textContent = 'Usage';
|
||||
|
||||
// Append all headers to row
|
||||
headerRow.appendChild(usernameHeader);
|
||||
headerRow.appendChild(joinDateHeader);
|
||||
headerRow.appendChild(messagesHeader);
|
||||
headerRow.appendChild(countryHeader);
|
||||
headerRow.appendChild(providerHeader);
|
||||
headerRow.appendChild(typeHeader);
|
||||
headerRow.appendChild(usageHeader);
|
||||
|
||||
thead.appendChild(headerRow);
|
||||
|
||||
// Create table body
|
||||
const tbody = document.createElement('tbody');
|
||||
|
||||
// Process each user entry
|
||||
userEntries.forEach((entry, index) => {
|
||||
const row = document.createElement('tr');
|
||||
row.style.cssText = `
|
||||
border-bottom: 1px solid #17191b;
|
||||
transition: background-color 0.2s;
|
||||
`;
|
||||
row.style.backgroundColor = index % 2 === 0 ? '#131313' : '#131313';
|
||||
|
||||
// Username
|
||||
const usernameLink = entry.querySelector('h3.contentRow-header a.username');
|
||||
const username = usernameLink ? usernameLink.textContent.trim() : 'N/A';
|
||||
const profileUrl = usernameLink ? usernameLink.href : '#';
|
||||
|
||||
// Check if user is banned
|
||||
const usernameSpan = entry.querySelector('h3.contentRow-header a.username span');
|
||||
const isBanned = usernameSpan && usernameSpan.classList.contains('username--banned');
|
||||
|
||||
// Join date
|
||||
const joinDateElement = entry.querySelector('time.u-dt');
|
||||
const joinDate = joinDateElement ? joinDateElement.textContent.trim() : 'N/A';
|
||||
|
||||
// Messages
|
||||
const dtElements = entry.querySelectorAll('dt');
|
||||
let messagesElement = null;
|
||||
for (const dt of dtElements) {
|
||||
if (dt.textContent.includes('Messages')) {
|
||||
messagesElement = dt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const messages = messagesElement ?
|
||||
(messagesElement.nextElementSibling ? messagesElement.nextElementSibling.textContent.trim() : '0') : '0';
|
||||
|
||||
// Country flag and name
|
||||
const flagElement = entry.querySelector('.tck-provider-country-flag');
|
||||
const countryCode = flagElement ? getCountryCode(flagElement) : '';
|
||||
const countryName = flagElement ? getCountryName(flagElement) : '';
|
||||
const flagEmoji = getFlagEmoji(countryCode);
|
||||
|
||||
// Provider info
|
||||
const providerElement = entry.querySelector('.tck-provider-txt');
|
||||
const providerInfo = providerElement ? getProviderInfo(providerElement) : { name: 'N/A', asn: '' };
|
||||
|
||||
// Type info
|
||||
const typeElement = entry.querySelector('.tck-provider-type');
|
||||
const typeInfo = typeElement ? getTypeInfo(typeElement) : 'N/A';
|
||||
|
||||
// Usage count
|
||||
const liElements = entry.querySelectorAll('li');
|
||||
let usageElement = null;
|
||||
for (const li of liElements) {
|
||||
if (li.textContent.includes('time')) {
|
||||
usageElement = li;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const usageText = usageElement ? usageElement.textContent.trim() : '0 time';
|
||||
const usageCount = getUsageCount(usageText);
|
||||
|
||||
|
||||
// Build row HTML
|
||||
row.innerHTML = `
|
||||
<td style="padding: 10px 12px; border-right: 1px solid #e5e7eb; white-space: nowrap;">
|
||||
<a href="${profileUrl}" target="_blank" style="color: ${isBanned ? '#dc2626' : '#2563eb'}; text-decoration: ${isBanned ? 'line-through' : 'none'}; font-weight: 500;">
|
||||
${isBanned ? '🚫 ' : ''}${username}
|
||||
</a>
|
||||
${isBanned ? '<span style="color: #dc2626; margin-left: 8px; font-size: 11px; font-weight: 600; background: #dc262620; padding: 2px 6px; border-radius: 3px;">BANNED</span>' : ''}
|
||||
</td>
|
||||
<td style="padding: 10px 12px; border-right: 1px solid #e5e7eb; white-space: nowrap; color: #6b7280;">${joinDate}</td>
|
||||
<td style="padding: 10px 12px; border-right: 1px solid #e5e7eb; white-space: nowrap; color: #6b7280;">${messages}</td>
|
||||
<td style="padding: 10px 12px; border-right: 1px solid #e5e7eb; white-space: nowrap;">
|
||||
<span title="${countryName}" style="color: #374151;">${flagEmoji} ${countryCode}</span>
|
||||
</td>
|
||||
<td style="padding: 10px 12px; border-right: 1px solid #e5e7eb; white-space: nowrap;" title="${providerInfo.asn}">
|
||||
<span style="color: #374151;">${providerInfo.name}</span>
|
||||
</td>
|
||||
<td style="padding: 10px 12px; border-right: 1px solid #e5e7eb; white-space: nowrap;" title="${typeInfo}">
|
||||
<span style="color: #6b7280;">${typeInfo.replace('Type: ', '')}</span>
|
||||
</td>
|
||||
<td style="padding: 10px 12px; text-align: center; white-space: nowrap;">
|
||||
<span style="color: #374151; font-weight: 500;">${usageCount}</span>
|
||||
</td>
|
||||
`;
|
||||
|
||||
// Add hover effect
|
||||
row.addEventListener('mouseenter', () => {
|
||||
row.style.backgroundColor = '#131313';
|
||||
});
|
||||
row.addEventListener('mouseleave', () => {
|
||||
row.style.backgroundColor = index % 2 === 0 ? '#131313' : '#131313';
|
||||
});
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
// Filter state
|
||||
let showBannedOnly = false;
|
||||
|
||||
// Function to apply both search and filter
|
||||
function applyFilters() {
|
||||
const searchTerm = searchInput.value.toLowerCase();
|
||||
const rows = tbody.querySelectorAll('tr');
|
||||
let visibleCount = 0;
|
||||
let bannedCount = 0;
|
||||
|
||||
rows.forEach(row => {
|
||||
const text = row.textContent.toLowerCase();
|
||||
const isBanned = row.querySelector('a[href*="/members/"]') &&
|
||||
row.querySelector('a[href*="/members/"]').textContent.includes('🚫');
|
||||
|
||||
const matchesSearch = text.includes(searchTerm);
|
||||
const matchesFilter = !showBannedOnly || isBanned;
|
||||
|
||||
const shouldShow = matchesSearch && matchesFilter;
|
||||
row.style.display = shouldShow ? '' : 'none';
|
||||
|
||||
if (shouldShow) {
|
||||
visibleCount++;
|
||||
if (isBanned) bannedCount++;
|
||||
}
|
||||
});
|
||||
|
||||
// Update count display
|
||||
if (showBannedOnly) {
|
||||
countDisplay.textContent = `Showing ${bannedCount} banned users`;
|
||||
} else {
|
||||
countDisplay.textContent = `${visibleCount} users (${bannedCount} banned)`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add search functionality
|
||||
searchInput.addEventListener('input', applyFilters);
|
||||
|
||||
// Add banned filter functionality
|
||||
bannedFilterButton.addEventListener('click', () => {
|
||||
showBannedOnly = !showBannedOnly;
|
||||
|
||||
if (showBannedOnly) {
|
||||
bannedFilterButton.textContent = 'Show All Users';
|
||||
bannedFilterButton.classList.add('active');
|
||||
bannedFilterButton.style.backgroundColor = '#dc2626';
|
||||
bannedFilterButton.style.color = 'white';
|
||||
} else {
|
||||
bannedFilterButton.textContent = 'Show Banned Only';
|
||||
bannedFilterButton.classList.remove('active');
|
||||
bannedFilterButton.style.backgroundColor = '#dc262620';
|
||||
bannedFilterButton.style.color = '#dc2626';
|
||||
}
|
||||
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
// Initialize filters to set up count display
|
||||
applyFilters();
|
||||
|
||||
// Assemble table
|
||||
table.appendChild(thead);
|
||||
table.appendChild(tbody);
|
||||
|
||||
// Add table to wrapper
|
||||
tableWrapper.appendChild(table);
|
||||
|
||||
// Add elements to container
|
||||
tableContainer.appendChild(searchFilterContainer);
|
||||
tableContainer.appendChild(tableWrapper);
|
||||
|
||||
// Sorting functionality
|
||||
let currentSort = { column: null, direction: 'asc' };
|
||||
|
||||
function sortTable(column, direction) {
|
||||
console.log('Sorting by:', column, direction); // Debug log
|
||||
|
||||
const rows = Array.from(tbody.querySelectorAll('tr'));
|
||||
console.log('Found rows:', rows.length); // Debug log
|
||||
|
||||
if (rows.length === 0) {
|
||||
console.log('No rows to sort');
|
||||
return;
|
||||
}
|
||||
|
||||
rows.sort((a, b) => {
|
||||
let aValue, bValue;
|
||||
|
||||
if (column === 'username') {
|
||||
const aLink = a.querySelector('a');
|
||||
const bLink = b.querySelector('a');
|
||||
aValue = aLink ? aLink.textContent.trim().toLowerCase() : '';
|
||||
bValue = bLink ? bLink.textContent.trim().toLowerCase() : '';
|
||||
} else if (column === 'type') {
|
||||
aValue = a.cells[5] ? a.cells[5].textContent.trim().toLowerCase() : '';
|
||||
bValue = b.cells[5] ? b.cells[5].textContent.trim().toLowerCase() : '';
|
||||
}
|
||||
|
||||
console.log('Comparing:', aValue, 'vs', bValue); // Debug log
|
||||
|
||||
if (direction === 'asc') {
|
||||
return aValue.localeCompare(bValue);
|
||||
} else {
|
||||
return bValue.localeCompare(aValue);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear tbody and re-append sorted rows
|
||||
tbody.innerHTML = '';
|
||||
rows.forEach(row => tbody.appendChild(row));
|
||||
|
||||
// Update sort icons
|
||||
document.querySelectorAll('[id$="-sort-icon"]').forEach(icon => {
|
||||
icon.textContent = '↕';
|
||||
});
|
||||
|
||||
const sortIcon = document.getElementById(`${column}-sort-icon`);
|
||||
if (sortIcon) {
|
||||
sortIcon.textContent = direction === 'asc' ? '↑' : '↓';
|
||||
}
|
||||
|
||||
console.log('Sorting completed'); // Debug log
|
||||
}
|
||||
|
||||
// Add click event listeners for sortable columns
|
||||
console.log('Adding click listener to username header');
|
||||
usernameHeader.addEventListener('click', (e) => {
|
||||
console.log('Username header clicked');
|
||||
e.preventDefault();
|
||||
if (currentSort.column === 'username') {
|
||||
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
currentSort.column = 'username';
|
||||
currentSort.direction = 'asc';
|
||||
}
|
||||
console.log('Current sort:', currentSort);
|
||||
sortTable(currentSort.column, currentSort.direction);
|
||||
});
|
||||
|
||||
// Add hover effects for username header
|
||||
usernameHeader.addEventListener('mouseenter', function() {
|
||||
this.style.backgroundColor = '#1a1a1a';
|
||||
});
|
||||
usernameHeader.addEventListener('mouseleave', function() {
|
||||
this.style.backgroundColor = '#101113';
|
||||
});
|
||||
|
||||
console.log('Adding click listener to type header');
|
||||
typeHeader.addEventListener('click', (e) => {
|
||||
console.log('Type header clicked');
|
||||
e.preventDefault();
|
||||
if (currentSort.column === 'type') {
|
||||
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
currentSort.column = 'type';
|
||||
currentSort.direction = 'asc';
|
||||
}
|
||||
console.log('Current sort:', currentSort);
|
||||
sortTable(currentSort.column, currentSort.direction);
|
||||
});
|
||||
|
||||
// Add hover effects for type header
|
||||
typeHeader.addEventListener('mouseenter', function() {
|
||||
this.style.backgroundColor = '#1a1a1a';
|
||||
});
|
||||
typeHeader.addEventListener('mouseleave', function() {
|
||||
this.style.backgroundColor = '#101113';
|
||||
});
|
||||
|
||||
// Replace overlay content
|
||||
overlay.innerHTML = '';
|
||||
overlay.appendChild(tableContainer);
|
||||
}
|
||||
|
||||
// Wait for overlay to load and then transform it
|
||||
function waitForOverlay() {
|
||||
const overlay = document.querySelector('.overlay-content');
|
||||
if (overlay) {
|
||||
createTable();
|
||||
} else {
|
||||
// Check again in 100ms
|
||||
setTimeout(waitForOverlay, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Start monitoring for overlay
|
||||
waitForOverlay();
|
||||
|
||||
// Also listen for overlay events in case it loads dynamically
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('js-overlayClose') ||
|
||||
e.target.closest('.js-overlayClose')) {
|
||||
// Overlay is closing, wait for it to reopen
|
||||
setTimeout(waitForOverlay, 500);
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
Loading…
x
Reference in New Issue
Block a user