Enhanced Context Menu

Enhanced context menu with additional features and customization

// ==UserScript==
// @name         Enhanced Context Menu
// @author       ALFHAZERO
// @namespace    http://
// @version      2.1.1
// @description  Enhanced context menu with additional features and customization
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// ==/UserScript==

(function() {
    'use strict';

    /* 1. CONSTANTS & GLOBAL VARIABLES */
    const STORAGE_KEYS = {
        PREFERENCES: 'userPreferences',
        HIDDEN_ELEMENTS: 'hiddenElements',
        BOOKMARKS: 'globalBookmarks'
    };

    /* 2. STYLES */
    const styles = {
        contextMenu: {
            position: 'fixed',
            backgroundColor: '#ffffff',
            border: '1px solid #cccccc',
            borderRadius: '4px',
            padding: '5px 0',
            minWidth: '200px',
            boxShadow: '0 2px 5px rgba(0,0,0,0.2)',
            zIndex: '9999999',
            fontSize: '14px',
            fontFamily: 'Arial, sans-serif'
        },
        menuItem: {
            padding: '8px 20px',
            cursor: 'pointer',
            listStyle: 'none',
            margin: '0',
            color: '#333333',
            transition: 'background-color 0.2s',
            display: 'flex',
            alignItems: 'center',
            gap: '8px'
        },
        divider: {
            height: '1px',
            backgroundColor: '#e0e0e0',
            margin: '5px 0'
        }
    };

    /* 3. DEFAULT PREFERENCES */
    const defaultPreferences = {
        showBookmark: true,
        showShare: true,
        showOpen: true,
        showViewImage: true,
        showHideElement: true,
        showSaveImage: true,
        showReload: true,
        showPrint: true,
        showOpenInNewTab: true,
        showCopyOptions: true,
        menuPosition: 'right',
        darkMode: false,
        fontSize: 'normal',
        menuStyle: 'default',
        menuAnimation: true,
        menuTransparency: 1,
        timeoutDuration: 3000,
        vibrateOnLongPress: true,
        swipeGestures: true,
        longPressDelay: 500,
        preventDefaultContextMenu: true,
        touchMoveThreshold: 10
    };

    /* 4. GLOBAL VARIABLES */
    let userPreferences = { ...defaultPreferences };
    let hiddenElements = [];
    let globalBookmarks = [];

    /* 5. UTILITY FUNCTIONS */
    function applyStyles(element, styleObject) {
        Object.assign(element.style, styleObject);
    }

    function removeContextMenu() {
        const existingMenu = document.querySelector('.context-menu');
        if (existingMenu) {
            existingMenu.remove();
        }
    }

    function vibrate(duration = 50) {
        if (userPreferences.vibrateOnLongPress && navigator.vibrate) {
            navigator.vibrate(duration);
        }
    }

    async function copyToClipboard(text) {
        try {
            await navigator.clipboard.writeText(text);
            showNotification('Berhasil disalin!');
        } catch (err) {
            // Fallback method for browsers that don't support clipboard API
            const textarea = document.createElement('textarea');
            textarea.value = text;
            document.body.appendChild(textarea);
            textarea.select();
            try {
                document.execCommand('copy');
                showNotification('Berhasil disalin!');
            } catch (e) {
                showNotification('Gagal menyalin teks');
            }
            document.body.removeChild(textarea);
        }
    }

    function showNotification(message, duration = 2000) {
        const notification = document.createElement('div');
        applyStyles(notification, {
            position: 'fixed',
            bottom: '20px',
            left: '50%',
            transform: 'translateX(-50%)',
            backgroundColor: 'rgba(0, 0, 0, 0.8)',
            color: '#ffffff',
            padding: '10px 20px',
            borderRadius: '4px',
            zIndex: '10000',
            transition: 'opacity 0.3s'
        });
        notification.textContent = message;
        document.body.appendChild(notification);

        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => notification.remove(), 300);
        }, duration);
    }

    function getSelectedText() {
        const selection = window.getSelection();
        return selection ? selection.toString().trim() : '';
    }

    function getLinkUrl(element) {
        const linkElement = element.closest('a') || (element.tagName === 'A' ? element : null);
        return linkElement ? linkElement.href : null;
    }

    function getImageUrl(element) {
        if (element.tagName === 'IMG') {
            return element.src;
        } else if (element.style.backgroundImage) {
            const match = element.style.backgroundImage.match(/url\(['"]?([^'"]+)['"]?\)/);
            return match ? match[1] : null;
        }
        return null;
    }

    function getElementText(element) {
        // Get only the text directly contained by this element, excluding child elements
        let text = '';
        for (let node of element.childNodes) {
            if (node.nodeType === Node.TEXT_NODE) {
                text += node.textContent.trim();
            }
        }
        return text || element.innerText.trim() || '';
    }


    /* 6. MANAGER FUNCTIONS */
    function createBookmarkManager() {
        removeContextMenu();
        
        const manager = document.createElement('div');
        applyStyles(manager, {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            backgroundColor: userPreferences.darkMode ? '#333' : '#fff',
            color: userPreferences.darkMode ? '#fff' : '#333',
            padding: '20px',
            borderRadius: '8px',
            boxShadow: '0 2px 10px rgba(0,0,0,0.2)',
            zIndex: '10000',
            maxWidth: '80%',
            maxHeight: '80vh',
            overflow: 'auto'
        });

        const title = document.createElement('h3');
        title.textContent = 'Bookmark Manager';
        title.style.marginTop = '0';
        manager.appendChild(title);

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '×';
        applyStyles(closeBtn, {
            position: 'absolute',
            right: '10px',
            top: '10px',
            border: 'none',
            background: 'none',
            fontSize: '20px',
            cursor: 'pointer',
            color: userPreferences.darkMode ? '#fff' : '#333'
        });
        closeBtn.onclick = () => manager.remove();
        manager.appendChild(closeBtn);

        if (globalBookmarks.length === 0) {
            const emptyMsg = document.createElement('p');
            emptyMsg.textContent = 'Tidak ada bookmark';
            manager.appendChild(emptyMsg);
        } else {
            const list = document.createElement('ul');
            applyStyles(list, {
                listStyle: 'none',
                padding: '0',
                margin: '0'
            });

            globalBookmarks.forEach((bookmark, index) => {
                const item = document.createElement('li');
                applyStyles(item, {
                    padding: '10px',
                    borderBottom: '1px solid ' + (userPreferences.darkMode ? '#555' : '#eee'),
                    display: 'flex',
                    justifyContent: 'space-between',
                    alignItems: 'center'
                });

                const link = document.createElement('a');
                link.href = bookmark.url;
                link.textContent = bookmark.title;
                link.target = '_blank';
                applyStyles(link, {
                    color: userPreferences.darkMode ? '#fff' : '#333',
                    textDecoration: 'none'
                });

                const deleteBtn = document.createElement('button');
                deleteBtn.textContent = 'Hapus';
                applyStyles(deleteBtn, {
                    padding: '5px 10px',
                    border: 'none',
                    borderRadius: '4px',
                    backgroundColor: '#ff4444',
                    color: '#fff',
                    cursor: 'pointer'
                });
                deleteBtn.onclick = () => {
                    globalBookmarks.splice(index, 1);
                    saveAllData();
                    item.remove();
                    if (globalBookmarks.length === 0) {
                        manager.querySelector('ul').remove();
                        const emptyMsg = document.createElement('p');
                        emptyMsg.textContent = 'Tidak ada bookmark';
                        manager.appendChild(emptyMsg);
                    }
                };

                item.appendChild(link);
                item.appendChild(deleteBtn);
                list.appendChild(item);
            });

            manager.appendChild(list);
        }

        document.body.appendChild(manager);
    }

    function createHiddenElementsManager() {
        removeContextMenu();
        
        const manager = document.createElement('div');
        applyStyles(manager, {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            backgroundColor: userPreferences.darkMode ? '#333' : '#fff',
            color: userPreferences.darkMode ? '#fff' : '#333',
            padding: '20px',
            borderRadius: '8px',
            boxShadow: '0 2px 10px rgba(0,0,0,0.2)',
            zIndex: '10000',
            maxWidth: '80%',
            maxHeight: '80vh',
            overflow: 'auto'
        });

        const title = document.createElement('h3');
        title.textContent = 'Hidden Elements Manager';
        title.style.marginTop = '0';
        manager.appendChild(title);

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '×';
        applyStyles(closeBtn, {
            position: 'absolute',
            right: '10px',
            top: '10px',
            border: 'none',
            background: 'none',
            fontSize: '20px',
            cursor: 'pointer',
            color: userPreferences.darkMode ? '#fff' : '#333'
        });
        closeBtn.onclick = () => manager.remove();
        manager.appendChild(closeBtn);

        if (hiddenElements.length === 0) {
            const emptyMsg = document.createElement('p');
            emptyMsg.textContent = 'Tidak ada elemen tersembunyi';
            manager.appendChild(emptyMsg);
        } else {
            const list = document.createElement('ul');
            applyStyles(list, {
                listStyle: 'none',
                padding: '0',
                margin: '0'
            });

            hiddenElements.forEach((selector, index) => {
                const item = document.createElement('li');
                applyStyles(item, {
                    padding: '10px',
                    borderBottom: '1px solid ' + (userPreferences.darkMode ? '#555' : '#eee'),
                    display: 'flex',
                    justifyContent: 'space-between',
                    alignItems: 'center'
                });

                const text = document.createElement('span');
                text.textContent = selector;

                const showBtn = document.createElement('button');
                showBtn.textContent = 'Tampilkan';
                applyStyles(showBtn, {
                    padding: '5px 10px',
                    border: 'none',
                    borderRadius: '4px',
                    backgroundColor: '#4CAF50',
                    color: '#fff',
                    cursor: 'pointer',
                    marginRight: '5px'
                });
                showBtn.onclick = () => {
                    try {
                        const elements = document.querySelectorAll(selector);
                        elements.forEach(el => {
                            el.style.display = '';
                        });
                        hiddenElements.splice(index, 1);
                        saveAllData();
                        item.remove();
                        if (hiddenElements.length === 0) {
                            manager.querySelector('ul').remove();
                            const emptyMsg = document.createElement('p');
                            emptyMsg.textContent = 'Tidak ada elemen tersembunyi';
                            manager.appendChild(emptyMsg);
                        }
                    } catch (e) {
                        console.error('Error showing element:', e);
                    }
                };

                item.appendChild(text);
                item.appendChild(showBtn);
                list.appendChild(item);
            });

            manager.appendChild(list);
        }

        document.body.appendChild(manager);
    }


    /* 7. CONTEXT MENU CREATION */
    function createContextMenu(target, x, y) {
        removeContextMenu();

        const menu = document.createElement('ul');
        menu.className = 'context-menu';
        applyStyles(menu, {
            ...styles.contextMenu,
            ...(userPreferences.darkMode ? {
                backgroundColor: '#333333',
                border: '1px solid #555555',
                color: '#ffffff'
            } : {}),
            opacity: userPreferences.menuTransparency
        });

        // Menu Items Array
        const menuItems = [];

        // Copy Options
        const selectedText = getSelectedText();
        if (selectedText) {
            menuItems.push({
                text: 'Salin Teks Terpilih',
                icon: '📋',
                action: () => copyToClipboard(selectedText)
            });
        }

        // Get element text (excluding child elements)
        const elementText = getElementText(target);
        if (elementText && elementText !== selectedText) {
            menuItems.push({
                text: 'Salin Teks Elemen',
                icon: '📝',
                action: () => copyToClipboard(elementText)
            });
        }

        // Image Options
        const imageUrl = getImageUrl(target);
        if (imageUrl) {
            if (userPreferences.showViewImage) {
                menuItems.push({
                    text: 'Lihat Gambar',
                    icon: '🖼️',
                    action: () => window.open(imageUrl, '_blank')
                });
            }
            if (userPreferences.showSaveImage) {
                menuItems.push({
                    text: 'Simpan Gambar',
                    icon: '💾',
                    action: () => {
                        const link = document.createElement('a');
                        link.href = imageUrl;
                        link.download = imageUrl.split('/').pop() || 'image';
                        link.click();
                    }
                });
            }
            menuItems.push({
                text: 'Salin URL Gambar',
                icon: '🔗',
                action: () => copyToClipboard(imageUrl)
            });
        }

        // Link Options
        const linkUrl = getLinkUrl(target);
        if (linkUrl) {
            menuItems.push({
                text: 'Salin Link',
                icon: '🔗',
                action: () => copyToClipboard(linkUrl)
            });

            if (userPreferences.showOpenInNewTab) {
                menuItems.push({
                    text: 'Buka di Tab Baru',
                    icon: '📑',
                    action: () => window.open(linkUrl, '_blank')
                });
            }
        }

        // Standard Menu Items
        if (userPreferences.showBookmark) {
            menuItems.push({
                text: 'Bookmark',
                icon: '⭐',
                action: () => {
                    const url = imageUrl || linkUrl || window.location.href;
                    const title = elementText || document.title;
                    globalBookmarks.push({ url, title });
                    saveAllData();
                    showNotification('Bookmark ditambahkan!');
                }
            });
        }

        if (userPreferences.showShare) {
            menuItems.push({
                text: 'Bagikan',
                icon: '📤',
                action: () => {
                    const url = imageUrl || linkUrl || window.location.href;
                    if (navigator.share) {
                        navigator.share({
                            title: document.title,
                            url: url
                        }).catch(console.error);
                    } else {
                        copyToClipboard(url);
                    }
                }
            });
        }

        if (userPreferences.showHideElement) {
            menuItems.push({
                text: 'Sembunyikan Elemen',
                icon: '👁️',
                action: () => {
                    target.style.display = 'none';
                    const selector = target.tagName.toLowerCase() + 
                        (target.className ? '.' + target.className.replace(/\s+/g, '.') : '');
                    hiddenElements.push(selector);
                    saveAllData();
                    showNotification('Elemen disembunyikan!');
                }
            });
        }

        // Management Options
        menuItems.push({
            text: 'Kelola Bookmark',
            icon: '📚',
            action: () => createBookmarkManager()
        });

        menuItems.push({
            text: 'Kelola Elemen Tersembunyi',
            icon: '👁️',
            action: () => createHiddenElementsManager()
        });

        if (userPreferences.showReload) {
            menuItems.push({
                text: 'Muat Ulang',
                icon: '🔄',
                action: () => location.reload()
            });
        }

        // Create Menu Items
        menuItems.forEach((item, index) => {
            if (index > 0) {
                const divider = document.createElement('li');
                applyStyles(divider, styles.divider);
                menu.appendChild(divider);
            }

            const menuItem = document.createElement('li');
            applyStyles(menuItem, styles.menuItem);
            
            menuItem.innerHTML = `<span class="menu-icon">${item.icon}</span><span>${item.text}</span>`;
            
            if (userPreferences.darkMode) {
                menuItem.style.color = '#ffffff';
            }

            menuItem.addEventListener('mouseover', () => {
                menuItem.style.backgroundColor = userPreferences.darkMode ? '#444444' : '#f0f0f0';
            });

            menuItem.addEventListener('mouseout', () => {
                menuItem.style.backgroundColor = 'transparent';
            });

            menuItem.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();
                item.action();
                removeContextMenu();
            });

            menu.appendChild(menuItem);
        });


        // Position Menu
        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;
        const menuWidth = 200; // Estimated menu width
        const menuHeight = menuItems.length * 40; // Estimated menu height

        let posX = x;
        let posY = y;

        // Check horizontal position
        if (x + menuWidth > viewportWidth) {
            posX = viewportWidth - menuWidth - 10;
        }

        // Check vertical position
        if (y + menuHeight > viewportHeight) {
            posY = viewportHeight - menuHeight - 10;
        }

        applyStyles(menu, {
            left: posX + 'px',
            top: posY + 'px'
        });

        document.body.appendChild(menu);

        // Auto hide menu after timeout
        if (userPreferences.timeoutDuration > 0) {
            setTimeout(() => {
                if (document.body.contains(menu)) {
                    menu.style.opacity = '0';
                    setTimeout(() => removeContextMenu(), 300);
                }
            }, userPreferences.timeoutDuration);
        }

        return menu;
    }

    /* 8. LONG PRESS HANDLER */
    function setupLongPress(element) {
        if (element.hasAttribute('data-longpress-initialized')) return;

        let timeoutId;
        let startX, startY;
        let isLongPress = false;
        const moveThreshold = userPreferences.touchMoveThreshold;

        const startLongPress = (e) => {
            const coords = e.type.includes('touch') ? 
                { x: e.touches[0].clientX, y: e.touches[0].clientY } :
                { x: e.clientX, y: e.clientY };

            startX = coords.x;
            startY = coords.y;
            isLongPress = false;

            timeoutId = setTimeout(() => {
                isLongPress = true;
                vibrate();
                createContextMenu(e.target, coords.x, coords.y);
            }, userPreferences.longPressDelay);
        };

        const moveHandler = (e) => {
            if (!timeoutId) return;

            const coords = e.type.includes('touch') ?
                { x: e.touches[0].clientX, y: e.touches[0].clientY } :
                { x: e.clientX, y: e.clientY };

            if (Math.abs(coords.x - startX) > moveThreshold || 
                Math.abs(coords.y - startY) > moveThreshold) {
                clearTimeout(timeoutId);
                timeoutId = null;
            }
        };

        const endLongPress = (e) => {
            clearTimeout(timeoutId);
            if (!isLongPress) return true;
            e.preventDefault();
            return false;
        };

        // Touch Events
        element.addEventListener('touchstart', startLongPress, { passive: true });
        element.addEventListener('touchmove', moveHandler, { passive: true });
        element.addEventListener('touchend', endLongPress);
        element.addEventListener('touchcancel', endLongPress);

        // Mouse Events
        element.addEventListener('mousedown', startLongPress);
        element.addEventListener('mousemove', moveHandler);
        element.addEventListener('mouseup', endLongPress);
        element.addEventListener('mouseleave', endLongPress);

        // Context Menu Prevention
        if (userPreferences.preventDefaultContextMenu) {
            element.addEventListener('contextmenu', (e) => e.preventDefault());
        }

        element.setAttribute('data-longpress-initialized', 'true');
    }

    /* 9. INITIALIZATION AND SETUP */
    function setupGlobalEvents() {
        document.addEventListener('click', (e) => {
            const contextMenu = document.querySelector('.context-menu');
            if (contextMenu && !contextMenu.contains(e.target)) {
                removeContextMenu();
            }
        });

        document.addEventListener('scroll', () => {
            removeContextMenu();
        });

        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') {
                removeContextMenu();
            }
        });
    }

    /* 10. STORAGE FUNCTIONS */
    function loadAllData() {
        try {
            const savedPreferences = GM_getValue(STORAGE_KEYS.PREFERENCES);
            if (savedPreferences) {
                userPreferences = { ...defaultPreferences, ...JSON.parse(savedPreferences) };
            }
            
            const savedHiddenElements = GM_getValue(STORAGE_KEYS.HIDDEN_ELEMENTS);
            if (savedHiddenElements) {
                hiddenElements = JSON.parse(savedHiddenElements);
            }
            
            const savedBookmarks = GM_getValue(STORAGE_KEYS.BOOKMARKS);
            if (savedBookmarks) {
                globalBookmarks = JSON.parse(savedBookmarks);
            }
        } catch (e) {
            console.error('Error loading data:', e);
        }
    }

    function saveAllData() {
        try {
            GM_setValue(STORAGE_KEYS.PREFERENCES, JSON.stringify(userPreferences));
            GM_setValue(STORAGE_KEYS.HIDDEN_ELEMENTS, JSON.stringify(hiddenElements));
            GM_setValue(STORAGE_KEYS.BOOKMARKS, JSON.stringify(globalBookmarks));
        } catch (e) {
            console.error('Error saving data:', e);
        }
    }

    /* 11. INITIALIZATION */
    function init() {
        loadAllData();
        setupGlobalEvents();
        
        const supportedElements = [
            'a', 'img', 'video',
            '[style*="background-image"]',
            '.product__sidebar__view__item',
            'div', 'span', 'p',
            'article', 'section',
            '.clickable',
            '[role="button"]',
            'button',
            'input[type="button"]',
            'input[type="submit"]',
            'iframe'
        ];
        
        supportedElements.forEach(selector => {
            document.querySelectorAll(selector).forEach(el => {
                if (!el.hasAttribute('data-longpress-initialized')) {
                    setupLongPress(el);
                }
            });
        });

        // Apply hidden elements
        hiddenElements.forEach(selector => {
            try {
                document.querySelectorAll(selector).forEach(el => {
                    el.style.display = 'none';
                });
            } catch (e) {
                console.error('Error applying hidden element:', e);
            }
        });

        // Auto save data every 5 minutes
        setInterval(saveAllData, 300000);
    }

    // Start initialization
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // Cleanup on page unload
    window.addEventListener('unload', saveAllData);

})();