// ==UserScript==
// @name KAAUH Lab Enhancement Suite (Buttons, Alerts, Auto-Actions)
// @namespace Violentmonkey Scripts
// @version 5.6
// @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
// @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
let scrolledToCL = false;
let scrolledToCH = false;
let scrolledToNoResult = false;
// --- 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: 'Arial', sans-serif !important;
font-size: 14px !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) {
if (eventKey.startsWith("CL_") || eventKey.startsWith("CH_")) {
const urlSpecificKey = `labSuiteNotified_${window.location.pathname}_${eventKey}`;
return sessionStorage.getItem(urlSpecificKey) === 'true';
} else {
return localStorage.getItem(`labSuiteNotified_${eventKey}`) === 'true';
}
}
function setNotificationFlag(eventKey) {
logDebug(`Setting notification flag for: ${eventKey}`);
if (eventKey.startsWith("CL_") || eventKey.startsWith("CH_")) {
const urlSpecificKey = `labSuiteNotified_${window.location.pathname}_${eventKey}`;
sessionStorage.setItem(urlSpecificKey, 'true');
} else {
localStorage.setItem(`labSuiteNotified_${eventKey}`, 'true');
}
}
function checkForIssues() {
// Reset scroll flags at the beginning of each check
scrolledToCL = false;
scrolledToCH = false;
scrolledToNoResult = false;
// logDebug("Checking for issues...");
const resultDivs = document.querySelectorAll(CONFIG.RESULT_CELL_SELECTOR);
const criticalDivs = document.querySelectorAll(CONFIG.CRITICAL_FLAG_SELECTOR);
let issueFoundInThisCycle = false;
// Check results for ">", "NO RESULT", "NO-XRESULT"
resultDivs.forEach(div => {
const text = div.textContent?.trim().toLowerCase() || '';
const eventKeyBase = text.replace(/\s+/g, '_'); // Create key for localStorage
const parentRow = div.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR);
if (text.includes(">")) {
const eventKey = `greaterThan_${parentRow?.dataset.rowId || text}`; // Make key more specific
if (!hasAlreadyNotified(eventKey)) {
if (parentRow) {
applyFlashingEffect([parentRow]);
showModal("Dilution is required for this sample (> detected)!");
setNotificationFlag(eventKey);
issueFoundInThisCycle = true;
}
}
} else if (text === "no result" || text === "no-xresult") {
const eventKey = `${eventKeyBase}_${parentRow?.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);
});
if (parentRow && !scrolledToNoResult) {
parentRow.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
scrolledToNoResult = true;
logDebug(`Scrolled to row with '${text}'`);
}
issueFoundInThisCycle = true; // Show modal is considered a match
}
}
});
// Check critical flags
criticalDivs.forEach(div => {
const text = div.textContent?.trim() || '';
const eventKey = `${text}_${div.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR)?.dataset.rowId || text}`; // Key based on flag and row
const parentRow = div.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR);
if (text === "CL") {
if (!hasAlreadyNotified(eventKey)) {
showModal("CRITICAL LOW RESULT DETECTED !!");
setNotificationFlag(eventKey);
if (parentRow && !scrolledToCL) {
parentRow.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
scrolledToCL = true;
logDebug("Scrolled to row with 'CL'");
}
issueFoundInThisCycle = true;
}
} else if (text === "CH") {
if (!hasAlreadyNotified(eventKey)) {
showModal("CRITICAL HIGH RESULT DETECTED !!");
setNotificationFlag(eventKey);
if (parentRow && !scrolledToCH) {
parentRow.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
scrolledToCH = true;
logDebug("Scrolled to row with 'CH'");
}
issueFoundInThisCycle = true;
}
}
});
return issueFoundInThisCycle;
}
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