diff --git a/shared-ip-manager.userscript.js b/shared-ip-manager.userscript.js
index b7d670d..2ebd510 100644
--- a/shared-ip-manager.userscript.js
+++ b/shared-ip-manager.userscript.js
@@ -1,7 +1,7 @@
// ==UserScript==
// @name Shared IP Manager
// @namespace http://tampermonkey.net/
-// @version 1.1.0
+// @version 1.1.1
// @description Transform shared IP overlay into searchable table format
// @author Ryahn
// @match *://*/*
@@ -59,7 +59,7 @@
// Find all user entries
const userEntries = overlay.querySelectorAll('.block-row.block-row--separated');
-
+
if (userEntries.length === 0) return;
// Create table container
@@ -112,7 +112,7 @@
transition: all 0.2s;
white-space: nowrap;
`;
-
+
// Add hover effect for button
bannedFilterButton.addEventListener('mouseenter', function() {
this.style.backgroundColor = '#dc2626';
@@ -166,47 +166,48 @@
// 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 ↕`;
-
+
// 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 ↕`;
-
- // Usage header
+
+ // Usage header (sortable)
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';
-
+ usageHeader.id = 'sort-usage';
+ usageHeader.style.cssText = `padding: 10px 12px; text-align: left; font-weight: 600; color: #959595; white-space: nowrap; cursor: pointer; user-select: none;`;
+ usageHeader.innerHTML = `Usage ↕`;
+
// Append all headers to row
headerRow.appendChild(usernameHeader);
headerRow.appendChild(joinDateHeader);
@@ -215,12 +216,12 @@
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');
@@ -234,15 +235,15 @@
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;
@@ -252,23 +253,23 @@
break;
}
}
- const messages = messagesElement ?
+ 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;
@@ -280,8 +281,8 @@
}
const usageText = usageElement ? usageElement.textContent.trim() : '0 time';
const usageCount = getUsageCount(usageText);
-
-
+
+
// Build row HTML
row.innerHTML = `
@@ -319,31 +320,31 @@
// 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/"]') &&
+ 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`;
@@ -351,14 +352,14 @@
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');
@@ -370,7 +371,7 @@
bannedFilterButton.style.backgroundColor = '#dc262620';
bannedFilterButton.style.color = '#dc2626';
}
-
+
applyFilters();
});
@@ -380,31 +381,31 @@
// 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');
@@ -413,34 +414,48 @@
} else if (column === 'type') {
aValue = a.cells[5] ? a.cells[5].textContent.trim().toLowerCase() : '';
bValue = b.cells[5] ? b.cells[5].textContent.trim().toLowerCase() : '';
+ } else if (column === 'usage') {
+ // Parse usage as numbers for proper numerical sorting
+ aValue = parseFloat(a.cells[6] ? a.cells[6].textContent.trim() : '0') || 0;
+ bValue = parseFloat(b.cells[6] ? b.cells[6].textContent.trim() : '0') || 0;
}
-
+
console.log('Comparing:', aValue, 'vs', bValue); // Debug log
-
- if (direction === 'asc') {
- return aValue.localeCompare(bValue);
+
+ if (column === 'usage') {
+ // Numerical comparison for usage
+ if (direction === 'asc') {
+ return aValue - bValue;
+ } else {
+ return bValue - aValue;
+ }
} else {
- return bValue.localeCompare(aValue);
+ // String comparison for username and type
+ 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) => {
@@ -455,7 +470,7 @@
console.log('Current sort:', currentSort);
sortTable(currentSort.column, currentSort.direction);
});
-
+
// Add hover effects for username header
usernameHeader.addEventListener('mouseenter', function() {
this.style.backgroundColor = '#1a1a1a';
@@ -463,7 +478,7 @@
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');
@@ -477,7 +492,7 @@
console.log('Current sort:', currentSort);
sortTable(currentSort.column, currentSort.direction);
});
-
+
// Add hover effects for type header
typeHeader.addEventListener('mouseenter', function() {
this.style.backgroundColor = '#1a1a1a';
@@ -486,6 +501,28 @@
this.style.backgroundColor = '#101113';
});
+ console.log('Adding click listener to usage header');
+ usageHeader.addEventListener('click', (e) => {
+ console.log('Usage header clicked');
+ e.preventDefault();
+ if (currentSort.column === 'usage') {
+ currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
+ } else {
+ currentSort.column = 'usage';
+ currentSort.direction = 'asc';
+ }
+ console.log('Current sort:', currentSort);
+ sortTable(currentSort.column, currentSort.direction);
+ });
+
+ // Add hover effects for usage header
+ usageHeader.addEventListener('mouseenter', function() {
+ this.style.backgroundColor = '#1a1a1a';
+ });
+ usageHeader.addEventListener('mouseleave', function() {
+ this.style.backgroundColor = '#101113';
+ });
+
// Replace overlay content
overlay.innerHTML = '';
overlay.appendChild(tableContainer);
@@ -507,7 +544,7 @@
// Also listen for overlay events in case it loads dynamically
document.addEventListener('click', (e) => {
- if (e.target.classList.contains('js-overlayClose') ||
+ if (e.target.classList.contains('js-overlayClose') ||
e.target.closest('.js-overlayClose')) {
// Overlay is closing, wait for it to reopen
setTimeout(waitForOverlay, 500);
|