GMRE Helper

Adds quality-of-life tweaks to Google Maps Road Editor.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(У мене вже є менеджер скриптів, дайте мені встановити його!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         GMRE Helper
// @namespace    https://github.com/gncnpk/gmre-helper
// @version      0.0.13
// @description  Adds quality-of-life tweaks to Google Maps Road Editor.
// @author       Gavin Canon-Phratsachack (https://github.com/gncnpk)
// @match        https://maps.google.com/roadeditor/iframe*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=google.com/maps
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const roadTypes = {
        "LOCAL_ROAD": 0,
        "HIGHWAY": 1,
        "PARKING_LOT": 2,
        "BIKING_WALKING_TRAIL": 3,
    };

    // Default key bindings configuration
    const defaultKeyBindings = {
        'i': {
            action: 'startNewRoad',
            description: 'Start New Road'
        },
        'Enter': {
            action: 'finishAction',
            description: 'Finish/Submit Action'
        },
        '1': {
            action: 'selectRoadType',
            param: roadTypes.LOCAL_ROAD,
            description: 'Select Local Road'
        },
        '2': {
            action: 'selectRoadType',
            param: roadTypes.HIGHWAY,
            description: 'Select Highway'
        },
        '3': {
            action: 'selectRoadType',
            param: roadTypes.PARKING_LOT,
            description: 'Select Parking Lot'
        },
        '4': {
            action: 'selectRoadType',
            param: roadTypes.BIKING_WALKING_TRAIL,
            description: 'Select Biking/Walking Trail'
        },
        'z': {
            action: 'undo',
            description: 'Undo'
        },
        'y': {
            action: 'redo',
            description: 'Redo'
        },
        'Delete': {
            action: 'deleteRoad',
            description: 'Delete Road'
        },
        's': {
            action: 'simplifyRoad',
            description: 'Simplify Road'
        },
        'Escape': {
            action: 'back',
            description: 'Back/Exit'
        },
        '+': {
            action: 'zoomIn',
            description: 'Zoom In'
        }, // ← new
        '-': {
            action: 'zoomOut',
            description: 'Zoom Out'
        }, // ← new
        'p': {
            action: 'markPrivateRoad',
            description: 'Mark Private Road'
        }, // ← new
        '`': {
            action: 'toggleSettings',
            description: 'Toggle Settings Panel'
        }
    };

    // Available actions
    const actions = {
        startNewRoad,
        finishAction,
        selectRoadType,
        undo,
        redo,
        deleteRoad,
        toggleSettings,
        simplifyRoad,
        back,
        zoomIn, // ← added
        zoomOut,
        markPrivateRoad
    };

    let keyBindings = {};
    let settingsPanel = null;
    let isSettingsOpen = false;

    function logConsole(msg) {
        console.log("Google Maps Road Editor Helper: " + msg);
    }

    function loadKeyBindings() {
        const stored = localStorage.getItem('gmre-helper-keybindings');
        if (stored) {
            try {
                keyBindings = {
                    ...defaultKeyBindings,
                    ...JSON.parse(stored)
                };
            } catch (e) {
                logConsole("Error loading saved key bindings, using defaults");
                keyBindings = {
                    ...defaultKeyBindings
                };
            }
        } else {
            keyBindings = {
                ...defaultKeyBindings
            };
        }
    }

    function saveKeyBindings() {
        const customBindings = {};
        Object.keys(keyBindings).forEach(key => {
            if (JSON.stringify(keyBindings[key]) !== JSON.stringify(defaultKeyBindings[key])) {
                customBindings[key] = keyBindings[key];
            }
        });
        localStorage.setItem('gmre-helper-keybindings', JSON.stringify(customBindings));
    }

    function addKeyBinding(key, actionName, param, description) {
        keyBindings[key] = {
            action: actionName,
            param: param,
            description: description || `${actionName}${param ? ` (${param})` : ''}`
        };
        saveKeyBindings();
        updateSettingsPanel();
    }

    function removeKeyBinding(key) {
        delete keyBindings[key];
        saveKeyBindings();
        updateSettingsPanel();
    }

    function setupKeyListener() {
        document.addEventListener("keydown", (e) => {
            // Don't trigger if user is typing in an input field
            if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) {
                return;
            }

            const key = e.key;
            const binding = keyBindings[key];

            if (binding && actions[binding.action]) {
                e.preventDefault();
                if (binding.param !== null && binding.param !== undefined) {
                    actions[binding.action](binding.param);
                } else {
                    actions[binding.action]();
                }
            }
        });
    }

    function createElement(tag, attributes = {}, textContent = '') {
        const element = document.createElement(tag);
        Object.entries(attributes).forEach(([key, value]) => {
            if (key === 'textContent') {
                element.textContent = value;
            } else {
                element.setAttribute(key, value);
            }
        });
        if (textContent) {
            element.textContent = textContent;
        }
        return element;
    }

    function createSettingsPanel() {
        // Create main panel
        settingsPanel = createElement('div', {
            id: 'gmre-settings-panel'
        });

        // Create content container
        const content = createElement('div', {
            id: 'gmre-settings-content'
        });

        // Create header
        const header = createElement('div', {
            id: 'gmre-settings-header'
        });
        const title = createElement('h3', {}, 'GMRE Helper - Key Bindings');
        const closeBtn = createElement('button', {
            id: 'gmre-close-settings'
        }, '×');
        header.appendChild(title);
        header.appendChild(closeBtn);

        // Create body
        const body = createElement('div', {
            id: 'gmre-settings-body'
        });

        // Create bindings list
        const bindingsList = createElement('div', {
            id: 'gmre-bindings-list'
        });

        // Create add binding section
        const addBinding = createElement('div', {
            id: 'gmre-add-binding'
        });
        const addTitle = createElement('h4', {}, 'Add New Binding');
        addBinding.appendChild(addTitle);

        // Key input
        const keyDiv = createElement('div');
        const keyLabel = createElement('label', {}, 'Key:');
        const keyInput = createElement('input', {
            type: 'text',
            id: 'gmre-new-key',
            placeholder: 'Enter key',
            maxlength: '10'
        });
        keyDiv.appendChild(keyLabel);
        keyDiv.appendChild(keyInput);

        // Action select
        const actionDiv = createElement('div');
        const actionLabel = createElement('label', {}, 'Action:');
        const actionSelect = createElement('select', {
            id: 'gmre-new-action'
        });

        const actionOptions = [{
                value: 'startNewRoad',
                text: 'Start New Road'
            },
            {
                value: 'finishAction',
                text: 'Finish Action'
            },
            {
                value: 'selectRoadType',
                text: 'Select Road Type'
            },
            {
                value: 'undo',
                text: 'Undo'
            },
            {
                value: 'redo',
                text: 'Redo'
            },
            {
                value: 'deleteRoad',
                text: 'Delete Road'
            },
            {
                value: 'simplifyRoad',
                text: 'Simplify Road'
            },
            {
                value: 'back',
                text: 'Back/Exit'
            },
            {
                value: 'toggleSettings',
                text: 'Toggle Settings Panel'
            },
            {
                value: 'zoomIn',
                text: 'Zoom In'
            }, // ← new
            {
                value: 'zoomOut',
                text: 'Zoom Out'
            }, // ← new
            {
                value: 'markPrivateRoad',
                text: 'Mark Private Road'
            }
        ];

        actionOptions.forEach(option => {
            const opt = createElement('option', {
                value: option.value
            }, option.text);
            actionSelect.appendChild(opt);
        });

        actionDiv.appendChild(actionLabel);
        actionDiv.appendChild(actionSelect);

        // Road type selector (hidden initially)
        const roadTypeDiv = createElement('div', {
            id: 'gmre-road-type-selector',
            style: 'display: none;'
        });
        const roadTypeLabel = createElement('label', {}, 'Road Type:');
        const roadTypeSelect = createElement('select', {
            id: 'gmre-new-param'
        });

        const roadTypeOptions = [{
                value: roadTypes.LOCAL_ROAD,
                text: 'Local Road'
            },
            {
                value: roadTypes.HIGHWAY,
                text: 'Highway'
            },
            {
                value: roadTypes.PARKING_LOT,
                text: 'Parking Lot'
            },
            {
                value: roadTypes.BIKING_WALKING_TRAIL,
                text: 'Biking/Walking Trail'
            }
        ];

        roadTypeOptions.forEach(option => {
            const opt = createElement('option', {
                value: option.value
            }, option.text);
            roadTypeSelect.appendChild(opt);
        });

        roadTypeDiv.appendChild(roadTypeLabel);
        roadTypeDiv.appendChild(roadTypeSelect);

        // Add button
        const addBtn = createElement('button', {
            id: 'gmre-add-btn'
        }, 'Add Binding');

        // Assemble add binding section
        addBinding.appendChild(keyDiv);
        addBinding.appendChild(actionDiv);
        addBinding.appendChild(roadTypeDiv);
        addBinding.appendChild(addBtn);

        // Create footer
        const footer = createElement('div', {
            id: 'gmre-settings-footer'
        });
        const resetBtn = createElement('button', {
            id: 'gmre-reset-defaults'
        }, 'Reset to Defaults');
        footer.appendChild(resetBtn);

        // Assemble body
        body.appendChild(bindingsList);
        body.appendChild(addBinding);
        body.appendChild(footer);

        // Assemble content
        content.appendChild(header);
        content.appendChild(body);

        // Assemble panel
        settingsPanel.appendChild(content);

        // Add styles
        const style = createElement('style');
        style.textContent = `
            #gmre-settings-panel {
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0, 0, 0, 0.8);
                z-index: 10000;
                display: none;
            }
            #gmre-settings-content {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background: white;
                border-radius: 8px;
                width: 500px;
                max-height: 600px;
                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
            }
            #gmre-settings-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 16px 20px;
                border-bottom: 1px solid #ddd;
                background: #f5f5f5;
                border-radius: 8px 8px 0 0;
            }
            #gmre-settings-header h3 {
                margin: 0;
                color: #333;
            }
            #gmre-close-settings {
                background: none;
                border: none;
                font-size: 24px;
                cursor: pointer;
                color: #666;
            }
            #gmre-settings-body {
                padding: 20px;
                max-height: 500px;
                overflow-y: auto;
            }
            .gmre-binding-item {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 8px 0;
                border-bottom: 1px solid #eee;
            }
            .gmre-binding-key {
                font-family: monospace;
                background: #f0f0f0;
                padding: 4px 8px;
                border-radius: 4px;
                font-weight: bold;
            }
            .gmre-binding-description {
                flex: 1;
                margin: 0 16px;
                color: #666;
            }
            .gmre-remove-btn {
                background: #ff4444;
                color: white;
                border: none;
                padding: 4px 8px;
                border-radius: 4px;
                cursor: pointer;
                font-size: 12px;
            }
            #gmre-add-binding {
                margin-top: 20px;
                padding-top: 20px;
                border-top: 2px solid #ddd;
            }
            #gmre-add-binding h4 {
                margin-top: 0;
                color: #333;
            }
            #gmre-add-binding div {
                margin-bottom: 12px;
            }
            #gmre-add-binding label {
                display: inline-block;
                width: 80px;
                font-weight: bold;
            }
            #gmre-add-binding input, #gmre-add-binding select {
                padding: 6px;
                border: 1px solid #ddd;
                border-radius: 4px;
                width: 150px;
            }
            #gmre-add-btn, #gmre-reset-defaults {
                background: #4285f4;
                color: white;
                border: none;
                padding: 8px 16px;
                border-radius: 4px;
                cursor: pointer;
                margin-right: 8px;
            }
            #gmre-reset-defaults {
                background: #ff6b6b;
            }
            #gmre-settings-footer {
                margin-top: 20px;
                padding-top: 16px;
                border-top: 1px solid #ddd;
            }
        `;

        document.head.appendChild(style);
        document.body.appendChild(settingsPanel);

        // Event listeners
        closeBtn.addEventListener('click', toggleSettings);
        actionSelect.addEventListener('change', function() {
            const roadTypeSelector = document.getElementById('gmre-road-type-selector');
            roadTypeSelector.style.display = this.value === 'selectRoadType' ? 'block' : 'none';
        });
        addBtn.addEventListener('click', addNewBinding);
        resetBtn.addEventListener('click', resetToDefaults);

        settingsPanel.addEventListener('click', function(e) {
            if (e.target === settingsPanel) {
                toggleSettings();
            }
        });

        updateSettingsPanel();
    }

    function updateSettingsPanel() {
        if (!settingsPanel) return;

        const bindingsList = document.getElementById('gmre-bindings-list');
        // Clear existing content
        while (bindingsList.firstChild) {
            bindingsList.removeChild(bindingsList.firstChild);
        }

        Object.entries(keyBindings).forEach(([key, binding]) => {
            const item = createElement('div', {
                class: 'gmre-binding-item'
            });

            const keySpan = createElement('span', {
                class: 'gmre-binding-key'
            }, key);
            const descSpan = createElement('span', {
                class: 'gmre-binding-description'
            }, binding.description);
            const removeBtn = createElement('button', {
                class: 'gmre-remove-btn',
                'data-key': key
            }, 'Remove');

            item.appendChild(keySpan);
            item.appendChild(descSpan);
            item.appendChild(removeBtn);
            bindingsList.appendChild(item);
        });

        // Add click listeners for remove buttons
        bindingsList.addEventListener('click', function(e) {
            if (e.target.classList.contains('gmre-remove-btn')) {
                const key = e.target.getAttribute('data-key');
                removeKeyBinding(key);
            }
        });
    }

    function addNewBinding() {
        const key = document.getElementById('gmre-new-key').value.trim();
        const action = document.getElementById('gmre-new-action').value;
        const param = action === 'selectRoadType' ?
            parseInt(document.getElementById('gmre-new-param').value) : undefined;

        if (!key || !action) {
            alert('Please fill in all required fields');
            return;
        }

        if (keyBindings[key]) {
            if (!confirm(`Key "${key}" is already bound. Replace it?`)) {
                return;
            }
        }

        const description = action === 'selectRoadType' ?
            `Select ${Object.keys(roadTypes).find(k => roadTypes[k] === param).replace('_', ' ')}` :
            action.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());

        addKeyBinding(key, action, param, description);

        // Clear form
        document.getElementById('gmre-new-key').value = '';
        document.getElementById('gmre-new-action').selectedIndex = 0;
        document.getElementById('gmre-road-type-selector').style.display = 'none';
    }

    function resetToDefaults() {
        if (confirm('Reset all key bindings to defaults? This cannot be undone.')) {
            keyBindings = {
                ...defaultKeyBindings
            };
            saveKeyBindings();
            updateSettingsPanel();
        }
    }

    function toggleSettings() {
        if (!settingsPanel) {
            createSettingsPanel();
        }

        isSettingsOpen = !isSettingsOpen;
        settingsPanel.style.display = isSettingsOpen ? 'block' : 'none';
    }

    // Action functions
    function startNewRoad() {
        try {
            document
                .getElementsByClassName(
                    "VfPpkd-LgbsSe VfPpkd-LgbsSe-OWXEXe-INsAgc VfPpkd-LgbsSe-OWXEXe-Bz112c-M1Soyc VfPpkd-LgbsSe-OWXEXe-dgl2Hf Rj2Mlf OLiIxf PDpWxe LQeN7 s73B3c wF1tve Q8G3mf",
                )[0]
                .children[2].click();
        } catch {
            logConsole("New road button not found...");
        }
    }

    function finishAction() {
        try {
            Array.from(document.getElementsByClassName("VfPpkd-RLmnJb")).filter((e) => {
                return e.parentElement.innerText === "Done" || e.parentElement.innerText === "Submit"
            })[0].click();
        } catch {
            logConsole("Finish button not found...");
        }
    }

    function selectRoadType(roadType) {
        try {
            document.getElementsByClassName("gzWBWb")[roadType].children[0].children[0].children[0].click();
            finishAction();
        } catch {
            logConsole("Road type option not found...");
        }
    }

    function undo() {
        try {
            document.getElementsByClassName("VfPpkd-LgbsSe VfPpkd-LgbsSe-OWXEXe-INsAgc VfPpkd-LgbsSe-OWXEXe-Bz112c-M1Soyc VfPpkd-LgbsSe-OWXEXe-dgl2Hf Rj2Mlf OLiIxf PDpWxe LQeN7 s73B3c MyHLpd zWXP4b Q8G3mf")[0].click();
        } catch {
            logConsole("Undo button not found...");
        }
    }

    function redo() {
        try {
            document.getElementsByClassName("VfPpkd-LgbsSe VfPpkd-LgbsSe-OWXEXe-INsAgc VfPpkd-LgbsSe-OWXEXe-Bz112c-M1Soyc VfPpkd-LgbsSe-OWXEXe-dgl2Hf Rj2Mlf OLiIxf PDpWxe LQeN7 s73B3c MyHLpd zWXP4b Q8G3mf")[1].click();
        } catch {
            logConsole("Redo button not found...");
        }
    }

    function deleteRoad() {
        try {
            document.getElementsByClassName("VfPpkd-muHVFf-bMcfAe")[4].click();
            finishAction();
        } catch {
            logConsole("Delete road button not found...");
        }
    }

    function back() {
        try {
            document.getElementsByClassName("VfPpkd-LgbsSe VfPpkd-LgbsSe-OWXEXe-INsAgc VfPpkd-LgbsSe-OWXEXe-Bz112c-M1Soyc VfPpkd-LgbsSe-OWXEXe-dgl2Hf Rj2Mlf OLiIxf PDpWxe LQeN7 s73B3c MyHLpd wphPJc Q8G3mf")[0].click();
        } catch {
            logConsole("Back button not found...");
        }
    }

    function zoomIn() {
        try {
            document
                .querySelectorAll(".VfPpkd-Bz112c-LgbsSe.yHy1rc.eT1oJ.mN1ivc.A07Gsf")[0]
                .click();
        } catch {
            logConsole("Zoom In button not found...");
        }
    }

    function zoomOut() {
        try {
            document
                .querySelectorAll(".VfPpkd-Bz112c-LgbsSe.yHy1rc.eT1oJ.mN1ivc.A07Gsf")[1]
                .click();
        } catch {
            logConsole("Zoom Out button not found...");
        }
    }

    function markPrivateRoad() {
        try {
            document.getElementsByClassName("VfPpkd-muHVFf-bMcfAe")[3].click();
        } catch {
            logConsole("Private road button not found...");
        }
    }

    function simplifyRoad() {
        try {
            const svg = document.getElementsByTagName("svg")[19];
            const nodeSelector = "H8Ty1d TNpQ1d CQUm1b";

            // Cache road selector queries
            const roadSelectors = [
                'path[stroke="#1a73e8"]',
                'path[stroke*="blue"]',
                'path[stroke="#4285f4"]'
            ];

            function ensureRoadSelected() {
                for (const selector of roadSelectors) {
                    const roadPath = document.querySelector(selector);
                    if (roadPath) {
                        roadPath.dispatchEvent(new MouseEvent('click', {
                            bubbles: true,
                            cancelable: true,
                            button: 0
                        }));
                        return true;
                    }
                }
                return false;
            }

            function deleteHalfNodes() {
                ensureRoadSelected();

                const initialNodes = document.getElementsByClassName(nodeSelector);
                const initialNodeCount = initialNodes.length;
                const targetNodeCount = Math.ceil(initialNodeCount / 2); // Keep half (rounded up)

                if (initialNodeCount === 0) {
                    logConsole("No nodes found to delete");
                    return;
                }

                logConsole(`Starting with ${initialNodeCount} nodes, target: ${targetNodeCount} nodes`);

                let maxAttempts = 100; // Maximum number of deletion attempts
                let attemptCount = 0;
                let previousNodeCount = initialNodeCount;
                let stuckCounter = 0;
                const maxStuckAttempts = 5; // Max attempts when stuck on same node count

                const processNodes = () => {
                    const nodes = document.getElementsByClassName(nodeSelector);

                    // Check termination conditions
                    if (nodes.length <= targetNodeCount) {
                        logConsole(`Target reached! Deleted ${initialNodeCount - nodes.length} nodes. ${nodes.length} nodes remaining.`);
                        return;
                    }

                    if (attemptCount >= maxAttempts) {
                        logConsole(`Stopped after ${maxAttempts} attempts. ${nodes.length} nodes remaining.`);
                        return;
                    }

                    // Check if we're stuck (same node count for multiple attempts)
                    if (nodes.length === previousNodeCount) {
                        stuckCounter++;
                        if (stuckCounter >= maxStuckAttempts) {
                            logConsole(`Stopped: unable to delete nodes after ${stuckCounter} attempts. ${nodes.length} nodes remaining.`);
                            return;
                        }
                    } else {
                        stuckCounter = 0; // Reset stuck counter if progress was made
                    }

                    previousNodeCount = nodes.length;
                    attemptCount++;

                    logConsole(`Attempt ${attemptCount}: ${nodes.length} nodes remaining (target: ${targetNodeCount})`);

                    const node = nodes[0];
                    const rect = node.getBoundingClientRect();
                    const xCoord = rect.left + rect.width / 2;
                    const yCoord = rect.top + rect.height / 2;

                    // Dispatch mouse events
                    svg.dispatchEvent(new MouseEvent('mousedown', {
                        clientX: xCoord,
                        clientY: yCoord,
                        bubbles: true,
                        cancelable: true,
                        button: 0
                    }));

                    svg.dispatchEvent(new MouseEvent('mouseup', {
                        clientX: xCoord,
                        clientY: yCoord,
                        bubbles: true,
                        cancelable: true,
                        button: 0
                    }));

                    // Use setTimeout instead of requestAnimationFrame for better control
                    setTimeout(() => {
                        const remainingNodes = document.getElementsByClassName(nodeSelector);

                        // If deletion didn't work, try clicking the element directly
                        if (remainingNodes.length === nodes.length && stuckCounter > 0) {
                            try {
                                const element = document.elementFromPoint(xCoord, yCoord);
                                if (element) {
                                    element.dispatchEvent(new MouseEvent('click', {
                                        clientX: xCoord,
                                        clientY: yCoord,
                                        bubbles: true,
                                        cancelable: true,
                                        button: 0
                                    }));
                                }
                            } catch (altError) {
                                logConsole("Alternative click method failed: " + altError.message);
                            }
                        }

                        // Continue processing if we haven't hit our limits and haven't reached target
                        if (attemptCount < maxAttempts &&
                            stuckCounter < maxStuckAttempts &&
                            remainingNodes.length > targetNodeCount) {
                            processNodes();
                        }
                    }, 50); // Small delay to allow UI to update
                };

                processNodes();
            }

            deleteHalfNodes();

        } catch (error) {
            logConsole("Error in simplifyRoad: " + error.message);
        }
    }

    // Add this function after the existing action functions
    function setupAutoRefreshWatcher() {
        function watchForAllDone() {
            const targetElement = document.getElementsByClassName("jfXz1e")[0];

            if (!targetElement) {
                // Element not found, try again in 1 second
                setTimeout(watchForAllDone, 1000);
                return;
            }

            // Create a MutationObserver to watch for text changes
            const observer = new MutationObserver(function(mutations) {
                mutations.forEach(function(mutation) {
                    if (mutation.type === 'childList' || mutation.type === 'characterData') {
                        const currentText = targetElement.innerText.trim();
                        if (currentText === 'All done') {
                            logConsole("'All done' detected - refreshing page...");
                            window.location.reload();
                        }
                    }
                });
            });

            // Configure the observer to watch for text changes
            observer.observe(targetElement, {
                childList: true,
                subtree: true,
                characterData: true
            });

            // Also check immediately in case the text is already there
            const currentText = targetElement.innerText.trim();
            if (currentText === 'All done') {
                logConsole("'All done' detected on load - refreshing page...");
                window.location.reload();
            }

            logConsole("Auto-refresh watcher set up for 'All done' status");
        }

        // Start watching after a short delay to ensure page is loaded
        setTimeout(watchForAllDone, 2000);
    }

    // Then modify your init() function to include this:
    async function init() {
        logConsole("Initializing with configurable key bindings...");
        loadKeyBindings();
        setupKeyListener();
        setupAutoRefreshWatcher(); // Add this line
        logConsole("Key bindings loaded. Press ` (backtick) to open settings.");
    }

    init();
})();