Enhanced Context Menu

Enhanced context menu with additional features and customization

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);

})();