Enhanced Context Menu

Enhanced context menu with additional features and customization

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);

})();