KAAUH Lab Enhancement Suite (Buttons, Alerts, Auto-Actions)

Combines verification buttons (F7/F8), dynamic alerts (>, NO RESULT, CL/CH) with modals/flashing, checkbox automation with exclusion/duplicates, toggle back-nav on toast click.

当前为 2025-04-01 提交的版本,查看 最新版本

// ==UserScript==
// @name         KAAUH Lab Enhancement Suite (Buttons, Alerts, Auto-Actions)
// @namespace    Violentmonkey Scripts
// @version      5.3
// @description  Combines verification buttons (F7/F8), dynamic alerts (>, NO RESULT, CL/CH) with modals/flashing, checkbox automation with exclusion/duplicates, toggle back-nav on toast click.
// @match        *://his.kaauh.org/lab/*
// @grant        none
// @author       Hamad AlShegifi (Combined & Refined by AI)
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // --- Configuration ---
    const CONFIG = {
        // General & Debugging
        DEBUG_MODE: true, // Enable/disable combined debug logs

        // Script 1: Buttons & Verification
        TARGET_EDIT_PAGE_URL_PREFIX: 'https://his.kaauh.org/lab/#/lab-orders/edit-lab-order/',
        EXCLUDE_WORDS: ['culture', "gram's stain", 'stain', 'bacterial', 'fungal', 'culture', 'pcr', 'Meningitis', 'MRSA', 'Mid', 'stream', 'Cryptococcus'],
        VERIFY1_BUTTON_SELECTOR: '#custom-script-buttons button.btn-success', // For F-key mapping
        VERIFY2_BUTTON_SELECTOR: '#custom-script-buttons button.btn-primary', // For F-key mapping
        COMPLETE_TECH_VERIFY_SELECTOR: 'button.dropdown-item[translateid="test-results.CompleteTechnicalVerification"]',
        COMPLETE_MED_VERIFY_SELECTOR: 'button.dropdown-item[translateid="test-results.CompleteMedicalVerification"]',
        FINAL_VERIFY_BUTTON_SELECTOR: 'button.btn-success.btn-sm.min-width[translateid="test-verification.Verify"]',
        NEXT_BUTTON_SELECTOR: 'button#btnNext',
        TEST_DESC_SELECTOR: 'div[col-id="TestDesc"]',
        UNCHECKED_BOX_SELECTOR: 'span.ag-icon-checkbox-unchecked[unselectable="on"]',
        CHECKBOX_PARENT_ROW_SELECTOR: '.ag-row', // Assumes AG Grid row class

        // Script 2: Alerts & Scanning
        SCAN_INTERVAL: 100, // Interval for issue scanning (ms)
        FLASH_COLOR: "pink", // Flashing color for rows with ">"
        FLASH_INTERVAL: 500, // Interval for flashing effect (ms)
        MODAL_TIMEOUT: 10000, // Timeout for modal auto-dismissal (ms)
        RESULT_CELL_SELECTOR: 'div[role="gridcell"][col-id="TestResult"] app-result-value-render div',
        CRITICAL_FLAG_SELECTOR: 'div[role="gridcell"][col-id="LTFlag"] app-ref-high-low div span.critial-alret-indication', // Typo kept if intentional

        // Script 3 & General Toast Handling
        UNDEFINED_URL_CHECK_INTERVAL: 200, // Interval for '/undefined' URL check (ms)
        TOAST_CONTAINER_SELECTOR: '#toast-container',
        TOAST_CLOSE_BUTTON_SELECTOR: 'button.toast-close-button',
        SUCCESS_TOAST_SELECTOR: '.toast-success', // For back-navigation observer
    };

    // --- State Variables ---
    // Script 1
    let verify1Toggle = localStorage.getItem('verify1Toggle') === 'true';
    let verify2Toggle = localStorage.getItem('verify2Toggle') === 'true';
    let verify1Clicked = false;
    let verify2Clicked = false;
    // Script 2
    let issueScanIntervalId = null; // To store the interval ID for issue scanning
    let isScanningActive = false; // Flag to prevent multiple scan intervals

    // --- Utility Functions ---
    function logDebug(message) {
        if (CONFIG.DEBUG_MODE) console.log(`[Lab Suite] ${message}`);
    }

    function loadFontAwesome() {
        const existingLink = document.querySelector('link[href*="font-awesome"]');
        if (!existingLink) {
            const link = document.createElement('link');
            link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css';
            link.rel = 'stylesheet';
            document.head.appendChild(link);
            logDebug('Font Awesome loaded');
        }
    }

    function isVisible(element) {
        return !!(element && (element.offsetWidth || element.offsetHeight || element.getClientRects().length));
    }

    // --- Modal Functions (from Script 2) ---
    function showModal(message, onConfirm) {
        // Remove existing modals first
        const existingOverlay = document.querySelector('.lab-suite-modal-overlay');
        if (existingOverlay) existingOverlay.remove();
        const existingModal = document.querySelector('.lab-suite-modal');
        if (existingModal) existingModal.remove();

        logDebug(`Showing modal: ${message}`);
        const overlay = document.createElement("div");
        overlay.className = 'lab-suite-modal-overlay'; // Add class for easier removal
        overlay.style.position = "fixed";
        overlay.style.top = "0";
        overlay.style.left = "0";
        overlay.style.width = "100%";
        overlay.style.height = "100%";
        overlay.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
        overlay.style.zIndex = "2000"; // Higher z-index
        overlay.style.opacity = "0";
        overlay.style.transition = "opacity 0.3s ease";
        document.body.appendChild(overlay);

        const modal = document.createElement("div");
        modal.className = 'lab-suite-modal'; // Add class for easier removal
        modal.style.position = "fixed";
        modal.style.top = "50%";
        modal.style.left = "50%";
        modal.style.transform = "translate(-50%, -50%) scale(0.5)";
        modal.style.backgroundColor = "#f4f4f9";
        modal.style.padding = "30px";
        modal.style.boxShadow = "0px 10px 30px rgba(0, 0, 0, 0.15)";
        modal.style.zIndex = "2001"; // Higher z-index
        modal.style.borderRadius = "15px";
        modal.style.textAlign = "center";
        modal.style.transition = "transform 0.3s ease, opacity 0.3s ease";
        modal.style.opacity = "0";
        modal.style.maxWidth = "450px"; // Limit width

        const heading = document.createElement("h2");
        heading.textContent = "Attention!";
        heading.style.fontFamily = "'Arial', sans-serif";
        heading.style.color = "#333";
        heading.style.marginBottom = "10px";
        heading.style.fontSize = "24px";
        modal.appendChild(heading);

        const content = document.createElement("p");
        content.textContent = message;
        content.style.fontFamily = "'Arial', sans-serif";
        content.style.color = "#555";
        content.style.marginBottom = "20px";
        content.style.fontSize = "16px";
        content.style.lineHeight = "1.5";
        modal.appendChild(content);

        let confirmButton, input; // Declare here for timeout access

        if (onConfirm) {
            input = document.createElement("input");
            input.type = "text";
            input.placeholder = "Enter your comment";
            input.style.padding = "10px";
            input.style.width = "calc(100% - 22px)"; // Adjust for padding/border
            input.style.marginBottom = "20px";
            input.style.border = "1px solid #ccc";
            input.style.borderRadius = "5px";
            input.style.fontSize = "16px";
            modal.appendChild(input);

            confirmButton = createModalButton("Confirm", "#ff4081", () => {
                const comment = input.value.trim();
                if (comment) {
                    logDebug(`Modal confirmed with comment: ${comment}`);
                    onConfirm(comment);
                    closeModal(modal, overlay);
                } else {
                    alert("Please enter a comment!");
                }
            });
            modal.appendChild(confirmButton);
        } else {
            const okButton = createModalButton("OK", "#ff4081", () => {
                logDebug("Modal OK button clicked.");
                closeModal(modal, overlay);
            });
            modal.appendChild(okButton);
        }

        document.body.appendChild(modal);

        // Animate appearance
        setTimeout(() => {
            overlay.style.opacity = "1";
            modal.style.transform = "translate(-50%, -50%) scale(1)";
            modal.style.opacity = "1";
        }, 10);

        // Auto-dismiss
        const timeoutId = setTimeout(() => {
            logDebug("Modal auto-dismissed.");
            closeModal(modal, overlay);
        }, CONFIG.MODAL_TIMEOUT);

        // Ensure timeout is cleared if manually closed
        modal.addEventListener('modalClosed', () => clearTimeout(timeoutId));
    }

    function createModalButton(text, backgroundColor, onClick) {
        const button = document.createElement("button");
        button.textContent = text;
        button.style.padding = "10px 20px";
        button.style.border = "none";
        button.style.backgroundColor = backgroundColor;
        button.style.color = "white";
        button.style.borderRadius = "30px";
        button.style.cursor = "pointer";
        button.style.fontSize = "16px";
        button.style.transition = "background-color 0.3s ease, transform 0.2s ease";
        button.style.minWidth = '80px';
        button.style.margin = '0 5px';

        button.addEventListener("mouseenter", () => {
            button.style.backgroundColor = darkenColor(backgroundColor, 20);
            button.style.transform = "scale(1.05)";
        });
        button.addEventListener("mouseleave", () => {
            button.style.backgroundColor = backgroundColor;
            button.style.transform = "scale(1)";
        });
        button.addEventListener("click", onClick);
        return button;
    }

    function darkenColor(color, percent) {
        try {
            let num = parseInt(color.slice(1), 16);
            let amt = Math.round(2.55 * percent);
            let R = (num >> 16) - amt;
            let G = ((num >> 8) & 0x00ff) - amt;
            let B = (num & 0x0000ff) - amt;
            R = R < 0 ? 0 : R; G = G < 0 ? 0 : G; B = B < 0 ? 0 : B; // Clamp at 0
            R = R > 255 ? 255 : R; G = G > 255 ? 255 : G; B = B > 255 ? 255 : B; // Clamp at 255
            return `#${(0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1)}`;
        } catch (e) {
            logDebug(`Error darkening color ${color}: ${e}`);
            return color; // Return original color on error
        }
    }

    function closeModal(modal, overlay) {
        if (!modal || !overlay || !document.body.contains(modal)) return; // Prevent errors if already removed
        logDebug("Closing modal.");
        modal.style.transform = "translate(-50%, -50%) scale(0.5)";
        modal.style.opacity = "0";
        overlay.style.opacity = "0";
        // Dispatch event to clear timeout
        modal.dispatchEvent(new Event('modalClosed'));
        setTimeout(() => {
            if (document.body.contains(modal)) document.body.removeChild(modal);
            if (document.body.contains(overlay)) document.body.removeChild(overlay);
        }, 300); // Match transition duration
    }


    // --- Script 1 Functions: Buttons & Verification ---
    function isCorrectPage() {
        return window.location.href.startsWith(CONFIG.TARGET_EDIT_PAGE_URL_PREFIX);
    }

    function createVerifyButton(label, className, onClick) {
        let button = document.createElement('button');
        button.type = 'button';
        button.innerHTML = label;
        button.className = className;

        let buttonColors = {
            'btn btn-success btn-sm': '#28a745', // Green for VERIFY1
            'btn btn-primary btn-sm': '#2594d9'  // Blue for VERIFY2
        };

        button.style.cssText = `
            font-family: system-ui; !important;
            font-size: 13px !important;
            font-weight: normal !important;
            color: #ffffff !important;
            background-color: ${buttonColors[className] || '#6c757d'} !important;
            padding: 8px 16px !important;
            border: none !important;
            border-radius: 5px !important;
            text-shadow: none !important;
            cursor: pointer !important;
            margin-right: 5px !important;
            line-height: 1.5 !important; /* Ensure text vertical alignment */
        `;
        button.onclick = onClick;
        return button;
    }

    function createToggleIcon(id, isActive, onClick) {
        let icon = document.createElement('span');
        icon.id = id;
        icon.innerHTML = `<i class="fa fa-arrow-circle-left" aria-hidden="true" style="color: ${isActive ? '#008000' : '#d1cfcf'}; font-size: 1.3em; vertical-align: middle;"></i>`;
        icon.style.cursor = 'pointer';
        icon.style.marginRight = '10px';
        icon.style.marginLeft = '-1px'; // Adjust spacing
        icon.onclick = onClick;
        icon.title = "Go back after this verification";
        return icon;
    }

    function handleVerify1IconToggle() {
        verify1Toggle = !verify1Toggle;
        localStorage.setItem('verify1Toggle', verify1Toggle);
        const iconElement = document.querySelector('#verify1Icon i');
        if (iconElement) iconElement.style.color = verify1Toggle ? '#008000' : '#d1cfcf';
        logDebug(`Verify1 Toggle set to: ${verify1Toggle}`);
    }

    function handleVerify2IconToggle() {
        verify2Toggle = !verify2Toggle;
        localStorage.setItem('verify2Toggle', verify2Toggle);
        const iconElement = document.querySelector('#verify2Icon i');
        if (iconElement) iconElement.style.color = verify2Toggle ? '#008000' : '#d1cfcf';
        logDebug(`Verify2 Toggle set to: ${verify2Toggle}`);
    }


    function addButtons() {
        if (document.getElementById('custom-script-buttons') || !isCorrectPage()) return; // Already added or wrong page

        const nextButton = document.querySelector(CONFIG.NEXT_BUTTON_SELECTOR);
        if (nextButton && nextButton.parentNode) {
            logDebug("Adding custom VERIFY buttons.");
            let buttonDiv = document.createElement('div');
            buttonDiv.id = 'custom-script-buttons';
            buttonDiv.style.display = 'inline-block';
            buttonDiv.style.marginLeft = '10px';
            buttonDiv.style.verticalAlign = 'middle'; // Align with Next button

            // VERIFY1 Button & Toggle
            let verify1Button = createVerifyButton('VERIFY1 (F7)', 'btn btn-success btn-sm', () => {
                logDebug("VERIFY1 button clicked.");
                verify1Clicked = true;
                verify2Clicked = false; // Ensure exclusivity
                checkAllVisibleBoxesWithoutDuplicates();
                setTimeout(() => { clickCompleteTechnicalVerificationButton(); }, 500);
            });
            let verify1Icon = createToggleIcon('verify1Icon', verify1Toggle, handleVerify1IconToggle);

            // VERIFY2 Button & Toggle
            let verify2Button = createVerifyButton('VERIFY2 (F8)', 'btn btn-primary btn-sm', () => {
                logDebug("VERIFY2 button clicked.");
                verify2Clicked = true;
                verify1Clicked = false; // Ensure exclusivity
                checkAllVisibleBoxesWithoutDuplicates();
                setTimeout(() => { clickCompleteMedicalVerificationButton(); }, 500);
            });
            let verify2Icon = createToggleIcon('verify2Icon', verify2Toggle, handleVerify2IconToggle);

            // Author Credit
            let modedByText = document.createElement('span');
            modedByText.textContent = "Moded by: Hamad AlShegifi";
            modedByText.style.fontSize = '12px';
            modedByText.style.fontWeight = 'bold';
            modedByText.style.color = '#ff0000';
            modedByText.style.position = 'relative';
            modedByText.style.left = '0px';
            modedByText.style.top = '0px';
            modedByText.style.border = '1.5px solid #ff0000';
            modedByText.style.borderRadius = '8px';
            modedByText.style.padding = '4px';
            modedByText.style.backgroundColor = 'white';

            buttonDiv.appendChild(verify1Button);
            buttonDiv.appendChild(verify1Icon);
            buttonDiv.appendChild(verify2Button);
            buttonDiv.appendChild(verify2Icon);
            buttonDiv.appendChild(modedByText);

            // Insert after the 'Next' button
            nextButton.parentNode.insertBefore(buttonDiv, nextButton.nextSibling);
        } else {
            logDebug("Could not find Next button to anchor custom buttons.");
        }
    }


    function checkAllVisibleBoxesWithoutDuplicates() {
        logDebug("Checking checkboxes...");
        const testDivs = document.querySelectorAll(CONFIG.TEST_DESC_SELECTOR);
        let seenTests = new Set();
        let boxesChecked = 0;
        const excludeWordsLower = CONFIG.EXCLUDE_WORDS.map(word => word.toLowerCase());

        testDivs.forEach(testDiv => {
            const testName = testDiv.textContent?.trim().toLowerCase() || '';
            if (!testName) return;

            // Skip if name contains excluded word
            if (excludeWordsLower.some(word => testName.includes(word))) {
                // logDebug(`Skipping excluded test: ${testName}`);
                return;
            }

            // Skip if duplicate
            if (seenTests.has(testName)) {
                // logDebug(`Skipping duplicate test: ${testName}`);
                return;
            }

            seenTests.add(testName);
            const parentRow = testDiv.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR);
            if (parentRow) {
                const checkbox = parentRow.querySelector(CONFIG.UNCHECKED_BOX_SELECTOR);
                // Check visibility robustly
                if (checkbox && isVisible(checkbox)) {
                    logDebug(`Clicking checkbox for test: ${testName}`);
                    const event = new MouseEvent('click', { bubbles: true, cancelable: true, view: window });
                    checkbox.dispatchEvent(event);
                    boxesChecked++;
                }
            }
        });
        logDebug(`${boxesChecked} unique, non-excluded checkboxes were checked.`);
    }

    function clickCompleteTechnicalVerificationButton() {
        const button = document.querySelector(CONFIG.COMPLETE_TECH_VERIFY_SELECTOR);
        if (button) {
            button.click();
            logDebug("Complete Technical Verification button clicked!");
            setTimeout(() => { clickFinalVerifyButton(); }, 700);
        } else {
            logDebug("Complete Technical Verification button not found!");
            alert("Error: Unable to find the 'Complete Technical Verification' button.");
        }
    }

    function clickCompleteMedicalVerificationButton() {
        const button = document.querySelector(CONFIG.COMPLETE_MED_VERIFY_SELECTOR);
        if (button) {
            button.click();
            logDebug("Complete Medical Verification button clicked!");
            setTimeout(() => { clickFinalVerifyButton(); }, 700);
        } else {
            logDebug("Complete Medical Verification button not found!");
            alert("Error: Unable to find the 'Complete Medical Verification' button.");
        }
    }

    function clickFinalVerifyButton() {
        const verifyButton = document.querySelector(CONFIG.FINAL_VERIFY_BUTTON_SELECTOR);
        if (verifyButton) {
            verifyButton.click();
            logDebug("Final Verify button clicked!");
        } else {
            logDebug("Final Verify button not found!");
            alert("Error: Unable to find the final 'Verify' button.");
        }
    }


    // --- Script 2 Functions: Alerts & Scanning ---
    function applyFlashingEffect(rows) {
        rows.forEach(row => {
            if (row.dataset.flashing) return; // Already flashing
            row.dataset.flashing = 'true'; // Mark as flashing
            logDebug("Applying flashing effect to a row.");

            row.style.transition = "background-color 0.5s ease";
            let isPink = false;
            const originalBg = row.style.backgroundColor || 'transparent';

            const intervalId = setInterval(() => {
                if (!document.contains(row)) { // Stop if row removed
                    clearInterval(intervalId);
                    delete row.dataset.flashing;
                    return;
                }
                isPink = !isPink;
                row.style.backgroundColor = isPink ? CONFIG.FLASH_COLOR : originalBg;
            }, CONFIG.FLASH_INTERVAL);

            // Store interval ID to potentially clear later if needed
            row.dataset.flashIntervalId = intervalId;
        });
    }

    function replacePlaceholderWithComment(divElement, comment) {
        if (divElement) {
            logDebug(`Replacing placeholder text with comment: ${comment}`);
            divElement.innerText = comment;
            // Optionally, trigger change events if the application needs it
            // divElement.dispatchEvent(new Event('input', { bubbles: true }));
            // divElement.dispatchEvent(new Event('change', { bubbles: true }));
        }
    }

    function hasAlreadyNotified(eventKey) {
        const notified = localStorage.getItem(`labSuiteNotified_${eventKey}`) === 'true';
        // logDebug(`Checked notification status for ${eventKey}: ${notified}`);
        return notified;
    }

    function setNotificationFlag(eventKey) {
        logDebug(`Setting notification flag for: ${eventKey}`);
        localStorage.setItem(`labSuiteNotified_${eventKey}`, 'true');
    }

    function checkForIssues() {
        // logDebug("Checking for issues...");
        const resultDivs = document.querySelectorAll(CONFIG.RESULT_CELL_SELECTOR);
        const criticalDivs = document.querySelectorAll(CONFIG.CRITICAL_FLAG_SELECTOR);
        let matchFound = false;

        // Check results for ">", "NO RESULT", "NO-XRESULT"
        resultDivs.forEach(div => {
            if (matchFound) return; // Stop checking if a match was already handled in this cycle
            const text = div.textContent?.trim().toLowerCase() || '';
            const eventKeyBase = text.replace(/\s+/g, '_'); // Create key for localStorage

            if (text.includes(">")) {
                const eventKey = `greaterThan_${div.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR)?.dataset.rowId || text}`; // Make key more specific
                if (!hasAlreadyNotified(eventKey)) {
                    const row = div.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR);
                    if (row) {
                        applyFlashingEffect([row]);
                        showModal("Dilution is required for this sample (> detected)!");
                        setNotificationFlag(eventKey);
                        matchFound = true;
                    }
                }
            } else if (text === "no result" || text === "no-xresult") {
                const eventKey = `${eventKeyBase}_${div.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR)?.dataset.rowId || text}`;
                if (!hasAlreadyNotified(eventKey)) {
                    showModal(`Please enter a comment to replace '${text.toUpperCase()}':`, (comment) => {
                        replacePlaceholderWithComment(div, comment);
                        // We set the flag *after* comment is entered and modal confirmed
                        setNotificationFlag(eventKey);
                    });
                    // Do not set flag here, set it in the callback
                    matchFound = true; // Show modal is considered a match
                }
            }
        });

        if (matchFound) return true; // Prioritize result issues over critical flags if found first

        // Check critical flags
        criticalDivs.forEach(div => {
            if (matchFound) return;
            const text = div.textContent?.trim() || '';
            const eventKey = `${text}_${div.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR)?.dataset.rowId || text}`; // Key based on flag and row

            if (text === "CL" && !hasAlreadyNotified(eventKey)) {
                showModal("CRITICAL LOW RESULT DETECTED !!");
                setNotificationFlag(eventKey);
                matchFound = true;
                // Scroll to the critical value
                div.scrollIntoView({ behavior: 'smooth', block: 'center' });
            } else if (text === "CH" && !hasAlreadyNotified(eventKey)) {
                showModal("CRITICAL HIGH RESULT DETECTED !!");
                setNotificationFlag(eventKey);
                matchFound = true;
                // Scroll to the critical value
                div.scrollIntoView({ behavior: 'smooth', block: 'center' });
            }
        });

        return matchFound;
    }

    function startContinuousScanning() {
        if (isScanningActive) {
            logDebug("Scanning is already active.");
            return;
        }
        logDebug("Starting continuous issue scanning...");
        isScanningActive = true;

        // Clear previous interval just in case
        if (issueScanIntervalId) clearInterval(issueScanIntervalId);

        issueScanIntervalId = setInterval(() => {
            if (!document.querySelector(CONFIG.RESULT_CELL_SELECTOR)) {
                logDebug("Result elements disappeared, stopping issue scan.");
                stopContinuousScanning();
                return;
            }
            const hasMatch = checkForIssues();
            if (hasMatch) {
                // The original script stopped on first match. Let's keep that behavior.
                // If multiple alerts per cycle are desired, remove this stop.
                // logDebug("Issue found and notified. Stopping scan cycle (will restart if elements remain).");
                // stopContinuousScanning(); // Let's actually let it run, but checkForIssues stops after first match internally
            }
        }, CONFIG.SCAN_INTERVAL);
    }

    function stopContinuousScanning() {
        if (issueScanIntervalId) {
            clearInterval(issueScanIntervalId);
            issueScanIntervalId = null;
            logDebug("Stopped continuous issue scanning.");
        }
        isScanningActive = false;
    }


    // --- Script 3 Function: Conditional Toast Click ---
    function checkUrlAndTriggerClickForUndefined() {
        const currentUrl = window.location.href;
        if (currentUrl.endsWith('/undefined')) {
            const toastContainer = document.querySelector(CONFIG.TOAST_CONTAINER_SELECTOR);
            if (toastContainer) {
                const closeButton = toastContainer.querySelector(CONFIG.TOAST_CLOSE_BUTTON_SELECTOR);
                // Check if button exists and is visible
                if (closeButton && isVisible(closeButton)) {
                    logDebug('URL ends with /undefined. Found visible toast close button. Clicking...');
                    closeButton.click();
                }
            }
        }
    }

    // --- Event Listeners & Observers ---

    // Keyboard Shortcuts (Script 1)
    document.addEventListener('keydown', function (event) {
        if (event.key === 'F7') {
            event.preventDefault(); // Prevent default F7 behavior
            logDebug("F7 pressed: Triggering VERIFY1 button click");
            const verify1Button = document.querySelector(CONFIG.VERIFY1_BUTTON_SELECTOR);
            if (verify1Button) verify1Button.click();
            else logDebug("VERIFY1 button not found for F7.");
        } else if (event.key === 'F8') {
            event.preventDefault(); // Prevent default F8 behavior
            logDebug("F8 pressed: Triggering VERIFY2 button click");
            const verify2Button = document.querySelector(CONFIG.VERIFY2_BUTTON_SELECTOR);
            if (verify2Button) verify2Button.click();
            else logDebug("VERIFY2 button not found for F8.");
        }
    });

    // Toast Observer for Back Navigation (Script 1)
    const toastObserver = new MutationObserver((mutations) => {
        mutations.forEach(mutation => {
            mutation.addedNodes.forEach(node => {
                if (node.nodeType === Node.ELEMENT_NODE && node.matches && node.matches(CONFIG.SUCCESS_TOAST_SELECTOR)) {
                    logDebug('Success toast added. Adding click listener for back navigation.');
                    node.addEventListener('click', () => {
                        logDebug('Success toast clicked.');
                        if (verify1Clicked && verify1Toggle) {
                            logDebug('Verify1 was clicked and toggle is on. Going back.');
                            window.history.back();
                        } else if (verify2Clicked && verify2Toggle) {
                            logDebug('Verify2 was clicked and toggle is on. Going back.');
                            window.history.back();
                        }
                        // Reset flags after handling
                        verify1Clicked = false;
                        verify2Clicked = false;
                    });
                }
            });
        });
    });

    // Main Observer for Page Changes & Initialization
    const mainObserver = new MutationObserver(() => {
        // Check for Script 1 buttons requirement
        if (isCorrectPage()) {
            addButtons(); // Function has internal check to prevent duplicates
        } else {
            // Remove buttons if not on the correct page
            const buttonDiv = document.getElementById('custom-script-buttons');
            if (buttonDiv) {
                logDebug("Leaving edit page, removing custom buttons.");
                buttonDiv.remove();
            }
        }

        // Check for Script 2 elements & start/stop scanning
        const resultDivsExist = document.querySelector(CONFIG.RESULT_CELL_SELECTOR);
        if (resultDivsExist) {
            if (!isScanningActive) {
                startContinuousScanning();
            }
        } else {
            if (isScanningActive) {
                // Elements disappeared, stop scanning
                stopContinuousScanning();
            }
        }
    });


    // --- Initialization ---
    try {
        logDebug("KAAUH Lab Enhancement Suite Initializing...");
        loadFontAwesome();

        // Start the continuous URL check for /undefined toasts (Script 3)
        setInterval(checkUrlAndTriggerClickForUndefined, CONFIG.UNDEFINED_URL_CHECK_INTERVAL);
        logDebug(`Started URL check interval (${CONFIG.UNDEFINED_URL_CHECK_INTERVAL}ms) for /undefined toasts.`);

        // Start observing the DOM for page changes and element readiness
        mainObserver.observe(document.body, { childList: true, subtree: true });
        logDebug("Started main MutationObserver.");

        // Start observing for success toast addition for back-navigation (Script 1)
        toastObserver.observe(document.body, { childList: true, subtree: true });
        logDebug("Started toast MutationObserver for back-navigation.");

        // Initial check on load
        window.addEventListener('load', () => {
            logDebug("Page fully loaded. Performing initial checks.");
            if (isCorrectPage()) { addButtons(); }
            if (document.querySelector(CONFIG.RESULT_CELL_SELECTOR) && !isScanningActive) {
                startContinuousScanning();
            }
        });

        logDebug("Initialization complete.");

    } catch (error) {
        console.error("[Lab Suite] Critical error during initialization:", error);
        alert("A critical error occurred in the Lab Enhancement Suite. Please check the browser console (F12) for details.");
    }

})(); // End of IIFE

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址