Remembers Hidden Elements + Use Hover based Highlights + Very Precise Selectors

This commit is contained in:
meowarex
2025-06-12 04:24:04 +10:00
parent 82145184df
commit e191ae50d6
3 changed files with 560 additions and 255 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@meowarex/element-hider", "name": "@meowarex/element-hider",
"description": "adds a right-click option to hide any element on the page.", "description": "Adds RightClick/Context menu options for hiding elements",
"author": { "author": {
"name": "meowarex", "name": "meowarex",
"url": "https://github.com/meowarex", "url": "https://github.com/meowarex",
@@ -0,0 +1,28 @@
import { ReactiveStore } from "@luna/core";
import { LunaSettings } from "@luna/ui";
import React from "react";
export const settings = await ReactiveStore.getPluginStorage("ElementHider", {
hiddenElements: [] as Array<{
selector: string;
tagName: string;
className: string;
textContent: string;
timestamp: number;
}>
});
export const Settings = () => {
const [hiddenElementsCount, setHiddenElementsCount] = React.useState(settings.hiddenElements.length);
// Update count when settings change
React.useEffect(() => {
const interval = setInterval(() => {
setHiddenElementsCount(settings.hiddenElements.length);
}, 1000);
return () => clearInterval(interval);
}, []);
return null;
};
+493 -216
View File
@@ -1,90 +1,317 @@
import { LunaUnload, Tracer } from "@luna/core"; import { LunaUnload, Tracer } from "@luna/core";
import { StyleTag, ContextMenu } from "@luna/lib"; import { StyleTag, ContextMenu } from "@luna/lib";
import { settings, Settings } from "./Settings";
// Import CSS directly using Luna's file:// syntax // Import CSS directly using Luna's file:// syntax
import styles from "file://styles.css?minify"; import styles from "file://styles.css?minify";
export const { trace } = Tracer("[Element Hider]"); export const { trace } = Tracer("[Element Hider]");
// Export Settings
export { Settings };
// Clean up resources // Clean up resources
export const unloads = new Set<LunaUnload>(); export const unloads = new Set<LunaUnload>();
// StyleTag for element hider styling // StyleTag for element hider
const styleTag = new StyleTag("Element-Hider", unloads, styles); const styleTag = new StyleTag("Element-Hider", unloads, styles);
// State management // State management
let targetElement: HTMLElement | null = null; let targetElement: HTMLElement | null = null;
let hiddenElements = new WeakSet<HTMLElement>(); let hiddenElements = new WeakSet<HTMLElement>();
let hiddenElementsArray: HTMLElement[] = []; // Keep array for iteration since WeakSet isn't iterable let hiddenElementsArray: HTMLElement[] = [];
// MutationObserver for reactive element detection
let elementObserver: MutationObserver | null = null;
// Generate a unique selector for an element
function generateElementSelector(element: HTMLElement): string {
// Priority 1: ID (most specific)
if (element.id) {
return `#${element.id}`;
}
// Hide element directly without animation (for restoration) // Priority 2: data-test attribute (very specific for Tidal <3)
const dataTest = element.getAttribute('data-test');
if (dataTest) {
return `[data-test="${dataTest}"]`;
}
// Priority 3: Combination of tag + specific classes + position
let selector = element.tagName.toLowerCase();
// Get filtered classes (exclude our temporary classes)
const classes = element.className ? element.className.trim().split(/\s+/).filter(cls => {
return cls.length > 0 &&
!cls.startsWith('element-hider-') &&
cls !== 'element-hider-target' &&
cls !== 'element-hider-hiding' &&
cls !== 'element-hider-hidden';
}) : [];
// Only use classes if we have them and they're not generic and dumb
if (classes.length > 0) {
// Use ALL classes to be very specific
selector += '.' + classes.join('.');
// Add parent context for extra specificity (for when the element is inside another element)
const parent = element.parentElement;
if (parent && parent.tagName !== 'BODY' && parent.tagName !== 'HTML') {
const parentClasses = parent.className ? parent.className.trim().split(/\s+/).filter(cls => {
return cls.length > 0 && !cls.startsWith('element-hider-');
}) : [];
if (parentClasses.length > 0) {
const parentSelector = parent.tagName.toLowerCase() + '.' + parentClasses.slice(0, 2).join('.');
selector = `${parentSelector} > ${selector}`;
}
}
} else {
// If no useful classes, use position-based selector with parent context
const parent = element.parentElement;
if (parent) {
const siblings = Array.from(parent.children).filter(child => child.tagName === element.tagName);
const index = siblings.indexOf(element);
if (index >= 0) {
selector += `:nth-of-type(${index + 1})`;
// Add parent context
if (parent.tagName !== 'BODY' && parent.tagName !== 'HTML') {
const parentClasses = parent.className ? parent.className.trim().split(/\s+/).filter(cls => {
return cls.length > 0 && !cls.startsWith('element-hider-');
}) : [];
if (parentClasses.length > 0) {
const parentSelector = parent.tagName.toLowerCase() + '.' + parentClasses.slice(0, 2).join('.');
selector = `${parentSelector} > ${selector}`;
}
}
}
}
}
console.log(`[Element Hider] Generated specific selector: ${selector}`);
return selector;
}
// Save hidden element to persistent storage | uses @Inrixia's Reactive Storage <3
function saveHiddenElement(element: HTMLElement): void {
const selector = generateElementSelector(element);
const elementInfo = {
selector: selector,
tagName: element.tagName,
className: element.className || '',
textContent: element.textContent?.substring(0, 100) || '',
timestamp: Date.now()
};
// Check if element is already saved
const existingIndex = settings.hiddenElements.findIndex(
stored => stored.selector === elementInfo.selector
);
if (existingIndex === -1) {
settings.hiddenElements.push(elementInfo);
console.log(`[Element Hider] Saved element: ${elementInfo.selector}`);
console.log(`[Element Hider] Total stored: ${settings.hiddenElements.length}`);
} else {
console.log(`[Element Hider] Element already saved: ${elementInfo.selector}`);
}
}
// Remove hidden element from persistent storage (for unhiding)
function removeSavedElement(element: HTMLElement): void {
const selector = generateElementSelector(element);
const index = settings.hiddenElements.findIndex(stored => stored.selector === selector);
if (index !== -1) {
settings.hiddenElements.splice(index, 1);
console.log(`[Element Hider] Permanently removed: ${selector}`);
console.log(`[Element Hider] Remaining stored: ${settings.hiddenElements.length}`);
}
}
// Check if an element matches any stored selector (EXACT match only)
function matchesStoredSelector(element: HTMLElement): boolean {
for (const storedElement of settings.hiddenElements) {
try {
// Only use EXACT selector match - no fallbacks during reactive hiding
if (element.matches(storedElement.selector)) {
return true;
}
} catch (error) {
console.warn(`[Element Hider] Invalid selector: ${storedElement.selector}`, error);
}
}
return false;
}
// Hide element directly without animation
function hideElementDirectly(element: HTMLElement): void { function hideElementDirectly(element: HTMLElement): void {
element.classList.add("element-hider-hidden"); if (hiddenElements.has(element)) return;
hiddenElements.add(element);
hiddenElementsArray.push(element); element.classList.add("element-hider-hidden");
hiddenElements.add(element);
hiddenElementsArray.push(element);
console.log(`[Element Hider] Hidden element: ${element.tagName}${element.className ? '.' + element.className.split(' ')[0] : ''}`);
} }
// Hide the target element with animation // Hide the target element with animation
function hideTargetElement(): void { function hideTargetElement(): void {
if (!targetElement) return; if (!targetElement) return;
trace.msg.log(`Hiding element: ${targetElement.tagName}${targetElement.className ? '.' + targetElement.className.split(' ').join('.') : ''}`); console.log(`[Element Hider] Hiding with animation: ${targetElement.tagName}${targetElement.className ? '.' + targetElement.className.split(' ')[0] : ''}`);
// Add hiding animation class // Add hiding animation class
targetElement.classList.add("element-hider-hiding"); targetElement.classList.add("element-hider-hiding");
// Store reference to the element // Store reference to the element
const elementToHide = targetElement; const elementToHide = targetElement;
// Wait for animation to complete, then hide // Save to persistent storage
setTimeout(() => { saveHiddenElement(elementToHide);
elementToHide.classList.add("element-hider-hidden");
elementToHide.classList.remove("element-hider-hiding", "element-hider-target");
hiddenElements.add(elementToHide);
hiddenElementsArray.push(elementToHide);
}, 300);
// Clear target reference // Wait for animation to complete, then hide
targetElement = null; setTimeout(() => {
elementToHide.classList.add("element-hider-hidden");
elementToHide.classList.remove("element-hider-hiding", "element-hider-target");
hiddenElements.add(elementToHide);
hiddenElementsArray.push(elementToHide);
}, 300);
// Clear target reference
targetElement = null;
} }
// Show all hidden elements // Unhide all elements permanently (remove from storage)
function showAllElements(): void { function unhideAllElements(): void {
trace.msg.log(`Showing ${hiddenElementsArray.length} hidden elements`); console.log(`[Element Hider] Permanently unhiding ${settings.hiddenElements.length} saved elements`);
// Use array to iterate and show all hidden elements // Show all currently hidden elements
hiddenElementsArray.forEach(element => { hiddenElementsArray.forEach(element => {
// Check if element is still in DOM if (document.body.contains(element)) {
if (document.body.contains(element)) { element.classList.remove("element-hider-hidden", "element-hider-hiding");
element.classList.remove("element-hider-hidden", "element-hider-hiding"); }
} });
});
// Clear both collections // Clear both storage and runtime collections
hiddenElements = new WeakSet<HTMLElement>(); settings.hiddenElements = [];
hiddenElementsArray = []; hiddenElements = new WeakSet<HTMLElement>();
hiddenElementsArray = [];
} }
// Handle highlighting target element on hover // Process all elements in the document to hide matching ones (with strict matching)
function processAllElements(): void {
if (settings.hiddenElements.length === 0) return;
console.log(`[Element Hider] Scanning document for ${settings.hiddenElements.length} stored selectors`);
let hiddenCount = 0;
// Use querySelectorAll for each stored selector with validation
settings.hiddenElements.forEach((storedElement, index) => {
try {
console.log(`[Element Hider] Searching for: ${storedElement.selector}`);
const elements = document.querySelectorAll(storedElement.selector);
console.log(`[Element Hider] Found ${elements.length} matches for selector ${index + 1}`);
// Limit to prevent over-hiding (safety check)
if (elements.length > 10) {
console.warn(`[Element Hider] Selector too broad (${elements.length} matches), skipping: ${storedElement.selector}`);
return;
}
elements.forEach((element, elemIndex) => {
const htmlElement = element as HTMLElement;
if (!hiddenElements.has(htmlElement)) {
hideElementDirectly(htmlElement);
hiddenCount++;
console.log(`[Element Hider] Hid element ${elemIndex + 1}/${elements.length} for selector ${index + 1}`);
}
});
} catch (error) {
console.warn(`[Element Hider] Invalid selector: ${storedElement.selector}`, error);
}
});
if (hiddenCount > 0) {
console.log(`[Element Hider] Total elements hidden: ${hiddenCount}`);
}
}
// Process new elements that are added to the DOM
function processNewElements(addedNodes: NodeList): void {
addedNodes.forEach(node => {
if (node.nodeType !== Node.ELEMENT_NODE) return;
const element = node as HTMLElement;
// Check the element itself
if (matchesStoredSelector(element)) {
hideElementDirectly(element);
}
// Check all descendant elements
const descendants = element.querySelectorAll('*');
descendants.forEach(descendant => {
if (matchesStoredSelector(descendant as HTMLElement)) {
hideElementDirectly(descendant as HTMLElement);
}
});
});
}
// Set up reactive element observer
function setupElementObserver(): void {
if (elementObserver) return;
elementObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
processNewElements(mutation.addedNodes);
}
});
});
elementObserver.observe(document.body, {
childList: true,
subtree: true
});
console.log(`[Element Hider] Set up reactive element observer`);
}
// Global functions
(window as any).showAllElementsFromSettings = unhideAllElements;
(window as any).debugElementHider = () => {
console.log(`=== Element Hider Debug Info ===`);
console.log(`Stored elements: ${settings.hiddenElements.length}`);
console.log(`Currently hidden elements: ${hiddenElementsArray.length}`);
console.log(`Reactive hiding enabled: true`);
settings.hiddenElements.forEach((element, index) => {
console.log(`${index + 1}. ${element.selector} (${element.tagName})`);
});
console.log(`================================`);
};
// Handle highlighting target element
function highlightElement(element: HTMLElement): void { function highlightElement(element: HTMLElement): void {
// Remove previous highlights // Remove previous highlights
document.querySelectorAll('.element-hider-target').forEach(el => { document.querySelectorAll('.element-hider-target').forEach(el => {
el.classList.remove('element-hider-target'); el.classList.remove('element-hider-target');
}); });
// Highlight current element // Highlight current element
element.classList.add('element-hider-target'); element.classList.add('element-hider-target');
targetElement = element; targetElement = element;
} }
// Remove highlight // Remove highlight
function removeHighlight(): void { function removeHighlight(): void {
if (targetElement) { if (targetElement) {
targetElement.classList.remove('element-hider-target'); targetElement.classList.remove('element-hider-target');
targetElement = null; targetElement = null;
} }
} }
// Context menu state management // Context menu state management
@@ -93,240 +320,290 @@ let customMenu: HTMLElement | null = null;
let contextMenuTimeout: number | null = null; let contextMenuTimeout: number | null = null;
let waitingForBuiltInMenu = false; let waitingForBuiltInMenu = false;
// Listen for right-click events to capture the target // Listen for right-click events to capture the target for context menu
document.addEventListener('contextmenu', (event: MouseEvent) => { document.addEventListener('contextmenu', (event: MouseEvent) => {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
// Don't interfere with native context menus on inputs, textareas, etc. // Don't interfere with native context menus on inputs, textareas, etc.
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) { if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
currentContextElement = null; currentContextElement = null;
return; return;
} }
// Don't show menu on our own custom menu // Don't show menu on our own custom menu
if (target.closest(".element-hider-custom-menu")) { if (target.closest(".element-hider-custom-menu")) {
return; return;
} }
// Close any existing custom menu // Close any existing custom menu
closeCustomMenu(); closeCustomMenu();
// Store the right-clicked element and context // Store the right-clicked element for context menu
currentContextElement = target; currentContextElement = target;
highlightElement(target); waitingForBuiltInMenu = true;
waitingForBuiltInMenu = true;
// Store event coordinates for potential custom menu // Store event coordinates for potential custom menu
const eventX = event.clientX; const eventX = event.clientX;
const eventY = event.clientY; const eventY = event.clientY;
// Wait to see if the built-in context menu appears // Wait to see if the built-in context menu appears
contextMenuTimeout = window.setTimeout(() => { contextMenuTimeout = window.setTimeout(() => {
// If we're still waiting and no built-in menu appeared, show our custom menu // If we're still waiting and no built-in menu appeared, show our custom menu
if (waitingForBuiltInMenu && currentContextElement) { if (waitingForBuiltInMenu && currentContextElement) {
event.preventDefault(); event.preventDefault();
showCustomMenu(eventX, eventY); showCustomMenu(eventX, eventY);
} }
waitingForBuiltInMenu = false; waitingForBuiltInMenu = false;
}, 150); // Wait 150ms for built-in menu }, 150); // Wait 150ms for built-in menu
// Don't prevent default initially - let Luna try to handle the context menu // Don't prevent default initially - let Luna try to handle the context menu
}, true); }, true);
// Listen for clicks to remove highlights and close custom menu // Listen for clicks to close custom menu
document.addEventListener('click', (event: MouseEvent) => { document.addEventListener('click', (event: MouseEvent) => {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
// If clicking outside our custom menu, close it // If clicking outside our custom menu, close it
if (customMenu && !target.closest(".element-hider-custom-menu")) { if (customMenu && !target.closest(".element-hider-custom-menu")) {
closeCustomMenu(); closeCustomMenu();
} removeHighlight();
}
removeHighlight();
}, true); }, true);
// Handle escape key to close custom menu // Handle escape key to close custom menu and remove highlights
document.addEventListener('keydown', (event: KeyboardEvent) => { document.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.key === "Escape" && customMenu) { if (event.key === "Escape") {
closeCustomMenu(); if (customMenu) {
removeHighlight(); closeCustomMenu();
} }
removeHighlight();
}
}); });
// Create custom context menu // Create custom context menu
function createCustomMenu(): HTMLElement { function createCustomMenu(): HTMLElement {
const menu = document.createElement("div"); const menu = document.createElement("div");
menu.className = "element-hider-custom-menu"; menu.className = "element-hider-custom-menu";
// Hide Element option // Hide Element option
const hideItem = document.createElement("button"); const hideItem = document.createElement("button");
hideItem.className = "element-hider-menu-item"; hideItem.className = "element-hider-menu-item";
hideItem.innerHTML = `Hide This Element`; hideItem.innerHTML = `Hide This Element`;
hideItem.addEventListener("click", () => { hideItem.addEventListener("click", () => {
if (currentContextElement) { if (currentContextElement) {
targetElement = currentContextElement; targetElement = currentContextElement;
hideTargetElement(); hideTargetElement();
closeCustomMenu(); closeCustomMenu();
} }
}); });
// Show All Elements option // Add hover effects for highlighting
const showAllItem = document.createElement("button"); hideItem.addEventListener("mouseenter", () => {
showAllItem.className = "element-hider-menu-item"; if (currentContextElement) {
showAllItem.innerHTML = `Show All Hidden Elements (${hiddenElementsArray.length})`; highlightElement(currentContextElement);
showAllItem.addEventListener("click", () => { }
showAllElements(); });
closeCustomMenu();
});
menu.appendChild(hideItem); hideItem.addEventListener("mouseleave", () => {
menu.appendChild(showAllItem); removeHighlight();
});
return menu; // Unhide All Elements option
const unhideAllItem = document.createElement("button");
unhideAllItem.className = "element-hider-menu-item";
unhideAllItem.innerHTML = `Unhide All Elements (${hiddenElementsArray.length})`;
unhideAllItem.addEventListener("click", () => {
unhideAllElements();
closeCustomMenu();
});
menu.appendChild(hideItem);
menu.appendChild(unhideAllItem);
return menu;
} }
// Show custom context menu // Show custom context menu
function showCustomMenu(x: number, y: number): void { function showCustomMenu(x: number, y: number): void {
closeCustomMenu(); closeCustomMenu();
customMenu = createCustomMenu(); customMenu = createCustomMenu();
document.body.appendChild(customMenu); document.body.appendChild(customMenu);
// Position the menu // Position the menu
const rect = customMenu.getBoundingClientRect(); const rect = customMenu.getBoundingClientRect();
const finalX = Math.min(x, window.innerWidth - rect.width - 10); const finalX = Math.min(x, window.innerWidth - rect.width - 10);
const finalY = Math.min(y, window.innerHeight - rect.height - 10); const finalY = Math.min(y, window.innerHeight - rect.height - 10);
customMenu.style.left = `${finalX}px`; customMenu.style.left = `${finalX}px`;
customMenu.style.top = `${finalY}px`; customMenu.style.top = `${finalY}px`;
trace.msg.log(`Custom context menu opened for element: ${currentContextElement?.tagName}${currentContextElement?.className ? '.' + currentContextElement.className.split(' ').join('.') : ''}`); console.log(`[Element Hider] Context menu opened for: ${currentContextElement?.tagName}`);
} }
// Close custom context menu // Close custom context menu
function closeCustomMenu(): void { function closeCustomMenu(): void {
if (customMenu) { if (customMenu) {
customMenu.remove(); customMenu.remove();
customMenu = null; customMenu = null;
} }
if (contextMenuTimeout) { if (contextMenuTimeout) {
clearTimeout(contextMenuTimeout); clearTimeout(contextMenuTimeout);
contextMenuTimeout = null; contextMenuTimeout = null;
} }
} }
// Try to hook into the context menu when it appears // Try to hook into the context menu when it appears
const contextMenuObserver = new MutationObserver((mutations) => { const contextMenuObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => { mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => { mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) { if (node.nodeType === Node.ELEMENT_NODE) {
const element = node as HTMLElement; const element = node as HTMLElement;
// Look for Tidal's context menu // Look for Tidal's context menu
if (element.matches('[data-test="contextmenu"]') || element.querySelector('[data-test="contextmenu"]')) { if (element.matches('[data-test="contextmenu"]') || element.querySelector('[data-test="contextmenu"]')) {
const contextMenu = element.matches('[data-test="contextmenu"]') ? element : element.querySelector('[data-test="contextmenu"]') as HTMLElement; const contextMenu = element.matches('[data-test="contextmenu"]') ? element : element.querySelector('[data-test="contextmenu"]') as HTMLElement;
if (contextMenu && currentContextElement && waitingForBuiltInMenu) { if (contextMenu && currentContextElement && waitingForBuiltInMenu) {
// Built-in menu appeared, cancel custom menu timeout // Built-in menu appeared, cancel custom menu timeout
waitingForBuiltInMenu = false; waitingForBuiltInMenu = false;
if (contextMenuTimeout) { if (contextMenuTimeout) {
clearTimeout(contextMenuTimeout); clearTimeout(contextMenuTimeout);
contextMenuTimeout = null; contextMenuTimeout = null;
} }
addElementHiderOptions(contextMenu); addElementHiderOptions(contextMenu);
} }
} }
} }
}); });
}); });
}); });
// Add our options to the existing context menu // Add our options to the existing context menu
function addElementHiderOptions(contextMenu: HTMLElement): void { function addElementHiderOptions(contextMenu: HTMLElement): void {
// Create hide element button // Create hide element button
const hideButton = document.createElement('button'); const hideButton = document.createElement('button');
hideButton.className = 'element-hider-menu-item'; hideButton.className = 'element-hider-menu-item';
hideButton.style.cssText = ` hideButton.style.cssText = `
display: flex; display: flex;
align-items: center; align-items: center;
padding: 8px 16px; padding: 8px 16px;
cursor: pointer; cursor: pointer;
color: var(--wave-color-text, #ffffff); color: var(--wave-color-text, #ffffff);
background: transparent; background: transparent;
border: none; border: none;
width: 100%; width: 100%;
text-align: left; text-align: left;
transition: background-color 0.15s ease; transition: background-color 0.15s ease;
font-family: inherit; font-family: inherit;
font-size: 14px; font-size: 14px;
`; `;
hideButton.innerHTML = `Hide This Element`; hideButton.innerHTML = `Hide This Element`;
hideButton.addEventListener('click', () => { hideButton.addEventListener('click', () => {
if (currentContextElement) { if (currentContextElement) {
targetElement = currentContextElement; targetElement = currentContextElement;
hideTargetElement(); hideTargetElement();
} }
}); });
// Create show all button // Add hover effects for highlighting
const showAllButton = document.createElement('button'); hideButton.addEventListener('mouseenter', () => {
showAllButton.className = 'element-hider-menu-item'; hideButton.style.background = 'var(--wave-color-background-hover, #3a3a3a)';
showAllButton.style.cssText = hideButton.style.cssText; if (currentContextElement) {
showAllButton.innerHTML = `Show All Hidden Elements (${hiddenElementsArray.length})`; highlightElement(currentContextElement);
}
});
showAllButton.addEventListener('click', showAllElements); hideButton.addEventListener('mouseleave', () => {
hideButton.style.background = 'transparent';
removeHighlight();
});
// Add hover effects // Create unhide all button
const buttons = [hideButton, showAllButton]; const unhideAllButton = document.createElement('button');
unhideAllButton.className = 'element-hider-menu-item';
unhideAllButton.style.cssText = hideButton.style.cssText;
unhideAllButton.innerHTML = `Unhide All Elements (${hiddenElementsArray.length})`;
buttons.forEach(button => { unhideAllButton.addEventListener('click', unhideAllElements);
button.addEventListener('mouseenter', () => {
button.style.background = 'var(--wave-color-background-hover, #3a3a3a)';
});
button.addEventListener('mouseleave', () => {
button.style.background = 'transparent';
});
});
// Add a separator if the menu has other items // Add hover effects for unhide all button
if (contextMenu.children.length > 0) { unhideAllButton.addEventListener('mouseenter', () => {
const separator = document.createElement('div'); unhideAllButton.style.background = 'var(--wave-color-background-hover, #3a3a3a)';
separator.style.cssText = ` });
height: 1px; unhideAllButton.addEventListener('mouseleave', () => {
background: var(--wave-color-border, #444); unhideAllButton.style.background = 'transparent';
margin: 4px 8px; });
`;
contextMenu.appendChild(separator);
}
// Add our buttons // Add a separator if the menu has other items
contextMenu.appendChild(hideButton); if (contextMenu.children.length > 0) {
contextMenu.appendChild(showAllButton); const separator = document.createElement('div');
separator.style.cssText = `
height: 1px;
background: var(--wave-color-border, #444);
margin: 4px 8px;
`;
contextMenu.appendChild(separator);
}
// Add our buttons
contextMenu.appendChild(hideButton);
contextMenu.appendChild(unhideAllButton);
} }
// Start observing for context menus // Start observing for context menus
contextMenuObserver.observe(document.body, { contextMenuObserver.observe(document.body, {
childList: true, childList: true,
subtree: true subtree: true
}); });
// Initialize plugin
function initializePlugin() {
console.log("[Element Hider] Initializing plugin...");
// Wait for DOM to be ready
setTimeout(() => {
console.log("[Element Hider] Starting element processing...");
// Process existing elements
processAllElements();
// Set up reactive observer for new elements
setupElementObserver();
console.log("[Element Hider] Plugin fully initialized");
}, 1000);
}
// Run initialization when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializePlugin);
} else {
initializePlugin();
}
// Add cleanup to unloads // Add cleanup to unloads
unloads.add(() => { unloads.add(() => {
// Stop observing for context menus // Stop observing
contextMenuObserver.disconnect(); if (elementObserver) {
elementObserver.disconnect();
elementObserver = null;
}
contextMenuObserver.disconnect();
// Close any open custom menu // Close any open custom menu
closeCustomMenu(); closeCustomMenu();
// Remove highlights // Remove highlights
removeHighlight(); removeHighlight();
// Show all hidden elements when plugin is unloaded // Clean up global functions
showAllElements(); delete (window as any).showAllElementsFromSettings;
delete (window as any).debugElementHider;
trace.msg.log("Element Hider plugin unloaded"); console.log("[Element Hider] Plugin unloaded");
}); });
trace.msg.log("Element Hider plugin loaded - Right-click any element to hide it!"); console.log("[Element Hider] Plugin loaded - Right-click any element to hide it!");