您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically fill Google Forms with predefined data
// ==UserScript== // @name Google Form Auto Filler with custom details // @namespace http://tampermonkey.net/ // @version 1.2 // @description Automatically fill Google Forms with predefined data // @author Bibek Chand Sah // @icon https://ssl.gstatic.com/docs/forms/device_home/android_192.png // @match https://docs.google.com/forms/* // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; // Helper function to create field mappings from tag-to-fields format function createFieldMappings(tagToFields) { const mappings = {}; Object.entries(tagToFields).forEach(([tag, fields]) => { fields.forEach(field => { mappings[field] = tag; }); }); return mappings; } // Default configuration with tags and field mappings let defaultTaggedData = { // Tags with their values tags: { 'ROLL_NUMBER': '22054', 'FULL_NAME': 'Your_name_here', 'STREAM': 'B.Tech', 'BRANCH': 'CSE', 'GENDER': 'Male', 'EMAIL': '@gmail.com', 'MOBILE': 'Your_no_here', 'TENTH_PERCENTAGE': '93.75', 'TENTH_YOP': '2020', 'TWELFTH_PERCENTAGE': '93.25', 'TWELFTH_YOP': '2021', 'GRADUATION_PERCENTAGE': '93.65', 'GRADUATION_YOP': '2026', 'NATIONALITY': 'Nepalese', 'BACKLOGS': '0', 'DATE_OF_BIRTH': '2002-12-28', 'BIRTH_DATE': '2002-12-28', 'DOB': '28/12/2002', 'GRADUATION_DATE': '2026-05-15' }, // Field mappings to tags - cleaner format with multiple fields per tag fieldMappings: createFieldMappings({ 'ROLL_NUMBER': ['Roll No', 'Roll'], 'FULL_NAME': ['Full Name', 'Name', 'Aadhar Name'], 'STREAM': ['Stream'], 'BRANCH': ['Branch'], 'GENDER': ['Gender'], 'EMAIL': ['Mail ID', 'Email'], 'MOBILE': ['Mobile No', 'Phone number'], 'TENTH_PERCENTAGE': ['10th %', '10t %', '10th Percentage'], 'TENTH_YOP': ['10th YOP'], 'TWELFTH_PERCENTAGE': ['12th/Diploma %', '12th Percentage'], 'TWELFTH_YOP': ['12th/Diploma YOP'], 'GRADUATION_PERCENTAGE': ['Graduation %'], 'GRADUATION_YOP': ['YOP'], 'NATIONALITY': ['Nationality'], 'BACKLOGS': ['No. of Backlogs','Backlogs'], 'DATE_OF_BIRTH': ['Date of Birth', 'Birth Date', 'DOB'], 'BIRTH_DATE': ['Birth Date', 'Date of Birth', 'DOB'], 'DOB': ['DOB', 'Date of Birth', 'Birth Date'], 'GRADUATION_DATE': ['Graduation Date', 'Expected Graduation'] }) }; // Load tagged data from localStorage or use defaults let taggedData = loadTaggedData(); // Track modal state for toggle functionality let isCustomizationModalOpen = false; // Error handling system const ErrorHandler = { // Error types with user-friendly messages errorTypes: { STORAGE_ERROR: { title: 'Storage Error', icon: '💾', color: '#ff5722', recoverable: true }, VALIDATION_ERROR: { title: 'Validation Error', icon: '⚠️', color: '#ff9800', recoverable: true }, FORM_FILL_ERROR: { title: 'Form Filling Error', icon: '📝', color: '#f44336', recoverable: true }, IMPORT_EXPORT_ERROR: { title: 'Import/Export Error', icon: '📁', color: '#e91e63', recoverable: true }, NETWORK_ERROR: { title: 'Network Error', icon: '🌐', color: '#9c27b0', recoverable: true }, UNKNOWN_ERROR: { title: 'Unexpected Error', icon: '❌', color: '#795548', recoverable: false } }, // Show enhanced error modal showError(errorType, message, details = null, recoveryOptions = []) { const errorInfo = this.errorTypes[errorType] || this.errorTypes.UNKNOWN_ERROR; // Log detailed error information console.group(`${errorInfo.icon} ${errorInfo.title}`); console.error('Message:', message); if (details) console.error('Details:', details); console.groupEnd(); // Create error modal this.createErrorModal(errorInfo, message, details, recoveryOptions); }, // Create error modal with recovery options createErrorModal(errorInfo, message, details, recoveryOptions) { // Remove existing error modal const existingModal = document.getElementById('error-modal-overlay'); if (existingModal) existingModal.remove(); // Create modal overlay const overlay = document.createElement('div'); overlay.id = 'error-modal-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 10001; display: flex; justify-content: center; align-items: center; font-family: Arial, sans-serif; animation: fadeIn 0.3s ease; `; // Create modal content const modal = document.createElement('div'); modal.style.cssText = ` background: white; border-radius: 16px; padding: 32px; max-width: 500px; max-height: 80vh; overflow-y: auto; box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4); text-align: center; position: relative; animation: slideIn 0.3s ease; `; // Create error icon and title const header = document.createElement('div'); header.style.cssText = 'margin-bottom: 24px;'; const icon = document.createElement('div'); icon.style.cssText = ` font-size: 48px; margin-bottom: 16px; animation: pulse 2s infinite; `; icon.textContent = errorInfo.icon; const title = document.createElement('h2'); title.style.cssText = ` margin: 0 0 8px 0; color: ${errorInfo.color}; font-size: 24px; font-weight: bold; `; title.textContent = errorInfo.title; header.appendChild(icon); header.appendChild(title); // Create error message const messageDiv = document.createElement('div'); messageDiv.style.cssText = ` color: #333; font-size: 16px; line-height: 1.5; margin-bottom: 24px; text-align: left; background: #f5f5f5; padding: 16px; border-radius: 8px; border-left: 4px solid ${errorInfo.color}; `; messageDiv.textContent = message; // Create details section (collapsible) let detailsSection = null; if (details) { detailsSection = document.createElement('div'); detailsSection.style.cssText = 'margin-bottom: 24px;'; const detailsToggle = document.createElement('button'); detailsToggle.style.cssText = ` background: none; border: none; color: #666; font-size: 14px; cursor: pointer; text-decoration: underline; margin-bottom: 12px; `; detailsToggle.textContent = '🔍 Show Technical Details'; const detailsContent = document.createElement('div'); detailsContent.style.cssText = ` display: none; background: #f8f8f8; padding: 12px; border-radius: 6px; font-family: monospace; font-size: 12px; color: #666; text-align: left; white-space: pre-wrap; max-height: 200px; overflow-y: auto; `; detailsContent.textContent = typeof details === 'object' ? JSON.stringify(details, null, 2) : details; detailsToggle.addEventListener('click', () => { const isVisible = detailsContent.style.display !== 'none'; detailsContent.style.display = isVisible ? 'none' : 'block'; detailsToggle.textContent = isVisible ? '🔍 Show Technical Details' : '🔼 Hide Technical Details'; }); detailsSection.appendChild(detailsToggle); detailsSection.appendChild(detailsContent); } // Create recovery options const recoverySection = document.createElement('div'); recoverySection.style.cssText = 'margin-bottom: 24px;'; if (recoveryOptions.length > 0) { const recoveryTitle = document.createElement('h3'); recoveryTitle.style.cssText = 'margin: 0 0 16px 0; color: #333; font-size: 18px;'; recoveryTitle.textContent = '🔧 Recovery Options'; recoverySection.appendChild(recoveryTitle); recoveryOptions.forEach((option, index) => { const optionButton = document.createElement('button'); optionButton.style.cssText = ` display: block; width: 100%; margin-bottom: 12px; padding: 12px 16px; background: ${option.primary ? '#4caf50' : '#2196f3'}; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: bold; cursor: pointer; transition: all 0.2s ease; `; optionButton.textContent = `${option.icon || '🔧'} ${option.text}`; optionButton.addEventListener('mouseenter', () => { optionButton.style.transform = 'translateY(-2px)'; optionButton.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)'; }); optionButton.addEventListener('mouseleave', () => { optionButton.style.transform = 'translateY(0)'; optionButton.style.boxShadow = 'none'; }); optionButton.addEventListener('click', () => { document.body.removeChild(overlay); if (option.action) option.action(); }); recoverySection.appendChild(optionButton); }); } // Create close button const closeButton = document.createElement('button'); closeButton.style.cssText = ` background: #666; color: white; border: none; padding: 12px 24px; border-radius: 8px; font-size: 14px; font-weight: bold; cursor: pointer; transition: background 0.2s ease; `; closeButton.textContent = '❌ Close'; closeButton.addEventListener('mouseenter', () => closeButton.style.background = '#555'); closeButton.addEventListener('mouseleave', () => closeButton.style.background = '#666'); closeButton.addEventListener('click', () => document.body.removeChild(overlay)); // Assemble modal modal.appendChild(header); modal.appendChild(messageDiv); if (detailsSection) modal.appendChild(detailsSection); modal.appendChild(recoverySection); modal.appendChild(closeButton); overlay.appendChild(modal); // Add CSS animations if (!document.getElementById('error-modal-styles')) { const style = document.createElement('style'); style.id = 'error-modal-styles'; style.textContent = ` @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideIn { from { transform: translateY(-50px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } } `; document.head.appendChild(style); } // Close on overlay click overlay.addEventListener('click', (e) => { if (e.target === overlay) { document.body.removeChild(overlay); } }); document.body.appendChild(overlay); }, // Quick error notification (non-blocking) showNotification(message, type = 'error', duration = 5000) { const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 10002; background: ${type === 'error' ? '#f44336' : type === 'warning' ? '#ff9800' : '#4caf50'}; color: white; padding: 16px 20px; border-radius: 8px; font-family: Arial, sans-serif; font-size: 14px; font-weight: bold; box-shadow: 0 4px 12px rgba(0,0,0,0.3); animation: slideInRight 0.3s ease; max-width: 300px; word-wrap: break-word; `; const icon = type === 'error' ? '❌' : type === 'warning' ? '⚠️' : '✅'; notification.textContent = `${icon} ${message}`; // Add slide animation if (!document.getElementById('notification-styles')) { const style = document.createElement('style'); style.id = 'notification-styles'; style.textContent = ` @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } `; document.head.appendChild(style); } document.body.appendChild(notification); // Auto-remove after duration setTimeout(() => { notification.style.animation = 'slideOutRight 0.3s ease'; setTimeout(() => { if (document.body.contains(notification)) { document.body.removeChild(notification); } }, 300); }, duration); // Click to dismiss notification.addEventListener('click', () => { notification.style.animation = 'slideOutRight 0.3s ease'; setTimeout(() => { if (document.body.contains(notification)) { document.body.removeChild(notification); } }, 300); }); } }; // Function to load tagged data from localStorage function loadTaggedData() { try { const saved = localStorage.getItem('googleFormFillerTaggedData'); if (saved) { const parsed = JSON.parse(saved); // Merge with defaults to ensure we have all base data return { tags: { ...defaultTaggedData.tags, ...parsed.tags }, fieldMappings: { ...defaultTaggedData.fieldMappings, ...parsed.fieldMappings } }; } } catch (error) { ErrorHandler.showError('STORAGE_ERROR', 'Failed to load your saved form data from browser storage.', error, [ { text: 'Use Default Data', icon: '🔄', primary: true, action: () => { console.log('Using default data due to storage error'); ErrorHandler.showNotification('Using default form data', 'warning'); } }, { text: 'Clear Storage & Restart', icon: '🗑️', action: () => { localStorage.removeItem('googleFormFillerTaggedData'); location.reload(); } } ] ); } return JSON.parse(JSON.stringify(defaultTaggedData)); // Deep copy } // Function to save tagged data to localStorage function saveTaggedData(data) { try { localStorage.setItem('googleFormFillerTaggedData', JSON.stringify(data)); console.log('✅ Tagged data saved to localStorage'); } catch (error) { ErrorHandler.showError('STORAGE_ERROR', 'Failed to save your form data to browser storage. Your changes may be lost.', error, [ { text: 'Retry Save', icon: '🔄', primary: true, action: () => { try { localStorage.setItem('googleFormFillerTaggedData', JSON.stringify(data)); ErrorHandler.showNotification('Data saved successfully!', 'success'); } catch (retryError) { ErrorHandler.showNotification('Save failed again. Check browser storage.', 'error'); } } }, { text: 'Export as Backup', icon: '📁', action: () => { exportDataAsJSON(); } } ] ); } } // Function to display current tag mappings in console function showCurrentMappings() { console.log('\n🏷️ CURRENT TAG MAPPINGS:'); console.log('========================'); // Group field mappings by tag const tagToFields = {}; Object.entries(taggedData.fieldMappings).forEach(([fieldName, tagName]) => { if (!tagToFields[tagName]) { tagToFields[tagName] = []; } tagToFields[tagName].push(fieldName); }); // Display each tag and its mapped fields Object.entries(tagToFields).forEach(([tagName, fields]) => { const tagValue = taggedData.tags[tagName] || 'No value set'; console.log(`\n🏷️ Tag: ${tagName}`); console.log(` Value: "${tagValue}"`); console.log(` Fields: ${fields.join(', ')}`); }); // Show unmapped tags const mappedTags = Object.values(taggedData.fieldMappings); const unmappedTags = Object.keys(taggedData.tags).filter(tag => !mappedTags.includes(tag)); if (unmappedTags.length > 0) { console.log('\n⚠️ UNMAPPED TAGS (have values but no field mappings):'); unmappedTags.forEach(tag => { console.log(` 🏷️ ${tag}: "${taggedData.tags[tag]}"`); }); } console.log('\n💡 Use showCurrentMappings() to view this again'); console.log('💡 Use the customize button to modify mappings'); } // Make functions available globally for console access window.showCurrentMappings = showCurrentMappings; // Global function to close customization modal (for keyboard shortcuts) window.closeCustomizationModal = function () { if (isCustomizationModalOpen) { // Find and close the modal const overlay = document.querySelector('#form-filler-modal-overlay'); if (overlay) { document.body.removeChild(overlay); isCustomizationModalOpen = false; console.log('🎹 Customization modal closed via keyboard shortcut'); return true; } } return false; }; // Data Export Functions function exportDataAsJSON() { try { const exportData = { version: "1.0", exportDate: new Date().toISOString(), tags: taggedData.tags, fieldMappings: taggedData.fieldMappings, metadata: { totalTags: Object.keys(taggedData.tags).length, totalMappings: Object.keys(taggedData.fieldMappings).length } }; const jsonString = JSON.stringify(exportData, null, 2); const blob = new Blob([jsonString], { type: 'application/json' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `google-form-filler-data-${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); console.log('📁 Data exported as JSON successfully'); alert('📁 Data exported as JSON file successfully!\n\nFile saved to your Downloads folder.'); return true; } catch (error) { ErrorHandler.showError('IMPORT_EXPORT_ERROR', 'Failed to export your data as JSON file.', error, [ { text: 'Try Again', icon: '🔄', primary: true, action: () => exportDataAsJSON() }, { text: 'Export as CSV Instead', icon: '📊', action: () => exportDataAsCSV() }, { text: 'Copy Data to Clipboard', icon: '📋', action: () => { try { const dataText = JSON.stringify(taggedData, null, 2); navigator.clipboard.writeText(dataText); ErrorHandler.showNotification('Data copied to clipboard!', 'success'); } catch (clipError) { ErrorHandler.showNotification('Failed to copy to clipboard', 'error'); } } } ] ); return false; } } function exportDataAsCSV() { try { // Create CSV content let csvContent = 'Type,Key,Value,Description\n'; // Add tags Object.entries(taggedData.tags).forEach(([tagName, value]) => { const escapedValue = `"${value.replace(/"/g, '""')}"`; csvContent += `Tag,${tagName},${escapedValue},Tag value\n`; }); // Add field mappings Object.entries(taggedData.fieldMappings).forEach(([fieldName, tagName]) => { const escapedFieldName = `"${fieldName.replace(/"/g, '""')}"`; csvContent += `Mapping,${escapedFieldName},${tagName},Field to tag mapping\n`; }); const blob = new Blob([csvContent], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `google-form-filler-data-${new Date().toISOString().split('T')[0]}.csv`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); console.log('📊 Data exported as CSV successfully'); alert('📊 Data exported as CSV file successfully!\n\nFile saved to your Downloads folder.'); return true; } catch (error) { ErrorHandler.showError('IMPORT_EXPORT_ERROR', 'Failed to export your data as CSV file.', error, [ { text: 'Try Again', icon: '🔄', primary: true, action: () => exportDataAsCSV() }, { text: 'Export as JSON Instead', icon: '📁', action: () => exportDataAsJSON() }, { text: 'View Data in Console', icon: '🔍', action: () => { console.table(taggedData.tags); console.table(taggedData.fieldMappings); ErrorHandler.showNotification('Data logged to console (F12)', 'success'); } } ] ); return false; } } // Data Import Functions function importDataFromJSON() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.addEventListener('change', (event) => { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { try { const importedData = JSON.parse(e.target.result); // Validate imported data structure if (!importedData.tags || !importedData.fieldMappings) { throw new Error('Invalid JSON format: missing tags or fieldMappings'); } // Merge with existing data (imported data takes precedence) const mergedData = { tags: { ...taggedData.tags, ...importedData.tags }, fieldMappings: { ...taggedData.fieldMappings, ...importedData.fieldMappings } }; // Update global data and save taggedData = mergedData; saveTaggedData(taggedData); const importedTags = Object.keys(importedData.tags).length; const importedMappings = Object.keys(importedData.fieldMappings).length; console.log('📁 JSON data imported successfully'); alert(`📁 JSON data imported successfully!\n\n✅ Imported ${importedTags} tags\n✅ Imported ${importedMappings} field mappings\n\nData has been merged with your existing data.`); } catch (error) { ErrorHandler.showError('IMPORT_EXPORT_ERROR', 'Failed to import JSON file. The file may be corrupted or in wrong format.', error, [ { text: 'Try Different File', icon: '📁', primary: true, action: () => importDataFromJSON() }, { text: 'Import CSV Instead', icon: '📊', action: () => importDataFromCSV() }, { text: 'Manual Data Entry', icon: '✏️', action: () => { // Close any existing modals and open customization setTimeout(() => showCustomizationModal(), 500); } } ] ); } }; reader.readAsText(file); }); input.click(); } function importDataFromCSV() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.csv'; input.addEventListener('change', (event) => { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { try { const csvContent = e.target.result; const lines = csvContent.split('\n'); // Skip header line const dataLines = lines.slice(1).filter(line => line.trim()); const importedTags = {}; const importedMappings = {}; dataLines.forEach(line => { const [type, key, value] = line.split(',').map(cell => cell.replace(/^"(.*)"$/, '$1').replace(/""/g, '"') ); if (type === 'Tag' && key && value) { importedTags[key] = value; } else if (type === 'Mapping' && key && value) { importedMappings[key] = value; } }); if (Object.keys(importedTags).length === 0 && Object.keys(importedMappings).length === 0) { throw new Error('No valid data found in CSV file'); } // Merge with existing data const mergedData = { tags: { ...taggedData.tags, ...importedTags }, fieldMappings: { ...taggedData.fieldMappings, ...importedMappings } }; // Update global data and save taggedData = mergedData; saveTaggedData(taggedData); const importedTagCount = Object.keys(importedTags).length; const importedMappingCount = Object.keys(importedMappings).length; console.log('📊 CSV data imported successfully'); alert(`📊 CSV data imported successfully!\n\n✅ Imported ${importedTagCount} tags\n✅ Imported ${importedMappingCount} field mappings\n\nData has been merged with your existing data.`); } catch (error) { ErrorHandler.showError('IMPORT_EXPORT_ERROR', 'Failed to import CSV file. The file may have incorrect format or encoding.', error, [ { text: 'Try Different File', icon: '📊', primary: true, action: () => importDataFromCSV() }, { text: 'Import JSON Instead', icon: '📁', action: () => importDataFromJSON() }, { text: 'Check CSV Format', icon: '📋', action: () => { const formatInfo = `Expected CSV format: Type,Key,Value,Description Tag,FULL_NAME,"John Doe",Tag value Tag,EMAIL,"[email protected]",Tag value Mapping,"Full Name",FULL_NAME,Field to tag mapping`; ErrorHandler.showError('IMPORT_EXPORT_ERROR', 'CSV Format Requirements', formatInfo, [{ text: 'Try Again', icon: '🔄', primary: true, action: () => importDataFromCSV() }] ); } } ] ); } }; reader.readAsText(file); }); input.click(); } // Field Validation Functions const validators = { email: { pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, message: 'Please enter a valid email address (e.g., [email protected])', test: (value) => validators.email.pattern.test(value) }, phone: { pattern: /^[\+]?[1-9][\d]{0,15}$|^[\+]?[(]?[\d\s\-\(\)]{10,}$/, message: 'Please enter a valid phone number (10+ digits, optional +, spaces, dashes, parentheses)', test: (value) => { const cleaned = value.replace(/[\s\-\(\)]/g, ''); return /^[\+]?[1-9][\d]{9,15}$/.test(cleaned); } }, percentage: { pattern: /^(100(\.0{1,2})?|[0-9]{1,2}(\.[0-9]{1,2})?)$/, message: 'Please enter a valid percentage (0-100, up to 2 decimal places)', test: (value) => { const num = parseFloat(value); return !isNaN(num) && num >= 0 && num <= 100; } }, year: { pattern: /^(19|20)\d{2}$/, message: 'Please enter a valid year (1900-2099)', test: (value) => { const year = parseInt(value); const currentYear = new Date().getFullYear(); return year >= 1900 && year <= currentYear + 10; } }, date: { pattern: /^\d{4}-\d{2}-\d{2}$|^\d{1,2}[\/\-]\d{1,2}[\/\-]\d{4}$/, message: 'Please enter a valid date (YYYY-MM-DD, DD/MM/YYYY, or DD-MM-YYYY)', test: (value) => { try { // Try to parse various date formats let dateValue = value; if (/^\d{4}-\d{2}-\d{2}$/.test(value)) { dateValue = value; } else if (/^\d{1,2}[\/\-]\d{1,2}[\/\-]\d{4}$/.test(value)) { const parts = value.split(/[\/\-]/); dateValue = `${parts[2]}-${parts[1].padStart(2, '0')}-${parts[0].padStart(2, '0')}`; } const date = new Date(dateValue); return !isNaN(date.getTime()) && date.getFullYear() >= 1900 && date.getFullYear() <= 2100; } catch { return false; } } }, rollNumber: { pattern: /^[A-Z0-9]{6,15}$/i, message: 'Please enter a valid roll number (6-15 alphanumeric characters)', test: (value) => validators.rollNumber.pattern.test(value) }, name: { pattern: /^[a-zA-Z\s]{2,50}$/, message: 'Please enter a valid name (2-50 characters, letters and spaces only)', test: (value) => validators.name.pattern.test(value.trim()) }, number: { pattern: /^\d+$/, message: 'Please enter a valid number (digits only)', test: (value) => validators.number.pattern.test(value) } }; // Function to detect field type based on field name function detectFieldType(fieldName) { const lowerField = fieldName.toLowerCase(); // Email detection if (lowerField.includes('email') || lowerField.includes('mail')) { return 'email'; } // Phone detection if (lowerField.includes('phone') || lowerField.includes('mobile') || lowerField.includes('contact')) { return 'phone'; } // Percentage detection if (lowerField.includes('%') || lowerField.includes('percentage') || lowerField.includes('percent')) { return 'percentage'; } // Date detection (full dates) if (lowerField.includes('date') || lowerField.includes('birth') || lowerField.includes('dob') || lowerField.includes('born') || lowerField.includes('graduation date')) { return 'date'; } // Year detection (year only fields) if (lowerField.includes('year') || lowerField.includes('yop') || lowerField.match(/\b(19|20)\d{2}\b/)) { return 'year'; } // Roll number detection if (lowerField.includes('roll') || lowerField.includes('registration') || lowerField.includes('student id')) { return 'rollNumber'; } // Name detection if (lowerField.includes('name') && !lowerField.includes('username') && !lowerField.includes('filename')) { return 'name'; } // Number detection (backlogs, count, etc.) if (lowerField.includes('backlog') || lowerField.includes('count') || lowerField.includes('number')) { return 'number'; } return null; // No specific validation needed } // Function to validate a single field function validateField(fieldName, value) { if (!value || value.trim() === '') { return { isValid: true, message: '' }; // Empty fields are allowed } const fieldType = detectFieldType(fieldName); if (!fieldType || !validators[fieldType]) { return { isValid: true, message: '' }; // No validation rule found } const validator = validators[fieldType]; const isValid = validator.test(value.trim()); return { isValid, message: isValid ? '' : validator.message, fieldType }; } // Function to validate all data function validateAllData(data) { const validationResults = {}; let hasErrors = false; Object.entries(data).forEach(([fieldName, value]) => { const result = validateField(fieldName, value); validationResults[fieldName] = result; if (!result.isValid) { hasErrors = true; } }); return { hasErrors, results: validationResults }; } // Function to auto-fix common validation errors function autoFixValidationErrors(validationResults) { const inputs = document.querySelectorAll('input[data-key]'); let fixedCount = 0; const fixedFields = []; inputs.forEach(input => { const tagName = input.getAttribute('data-key'); const validation = validationResults.results[tagName]; if (validation && !validation.isValid) { const currentValue = input.value.trim(); let fixedValue = null; // Auto-fix common issues based on field type switch (validation.fieldType) { case 'email': // Fix common email issues if (currentValue && !currentValue.includes('@')) { fixedValue = `${currentValue}@gmail.com`; } else if (currentValue.includes('@') && !currentValue.includes('.')) { fixedValue = `${currentValue}.com`; } else if (currentValue.includes(' ')) { fixedValue = currentValue.replace(/\s+/g, ''); } break; case 'phone': // Fix phone number formatting if (currentValue) { const digitsOnly = currentValue.replace(/\D/g, ''); if (digitsOnly.length === 10) { fixedValue = digitsOnly; } else if (digitsOnly.length === 11 && digitsOnly.startsWith('1')) { fixedValue = digitsOnly.substring(1); } } break; case 'percentage': // Fix percentage values if (currentValue) { const numValue = parseFloat(currentValue.replace('%', '')); if (!isNaN(numValue) && numValue >= 0 && numValue <= 100) { fixedValue = numValue.toString(); } } break; case 'year': // Fix year values if (currentValue) { const yearMatch = currentValue.match(/\d{4}/); if (yearMatch) { const year = parseInt(yearMatch[0]); const currentYear = new Date().getFullYear(); if (year >= 1900 && year <= currentYear + 10) { fixedValue = year.toString(); } } } break; case 'name': // Fix name formatting if (currentValue) { fixedValue = currentValue .replace(/[^a-zA-Z\s]/g, '') // Remove non-letter characters .replace(/\s+/g, ' ') // Replace multiple spaces with single space .trim() .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(' '); } break; case 'number': // Fix number values if (currentValue) { const numValue = currentValue.replace(/\D/g, ''); if (numValue) { fixedValue = numValue; } } break; } // Apply the fix if we found one if (fixedValue && fixedValue !== currentValue) { input.value = fixedValue; // Trigger validation to update UI const newValidation = validateField(tagName, fixedValue); addValidationFeedback(input, newValidation); fixedCount++; fixedFields.push(`${tagName}: "${currentValue}" → "${fixedValue}"`); console.log(`🔧 Auto-fixed ${tagName}: "${currentValue}" → "${fixedValue}"`); } } }); // Show results if (fixedCount > 0) { const fixedList = fixedFields.join('\n'); ErrorHandler.showNotification(`🔧 Auto-Fixed ${fixedCount} field${fixedCount > 1 ? 's' : ''}! Please review and save.`, 'success'); console.log(`🔧 Auto-fixed fields:\n${fixedList}`); // Update validation summary setTimeout(() => { const summaryElement = document.querySelector('#validation-summary'); if (summaryElement && window.updateValidationSummary) { window.updateValidationSummary(); } }, 100); } else { ErrorHandler.showNotification('🔧 No automatic fixes available. Please review fields manually.', 'warning'); } } // Function to show detailed validation suggestions function showValidationSuggestions(validationResults) { const errorFields = Object.entries(validationResults.results) .filter(([_, result]) => !result.isValid); let suggestions = '💡 VALIDATION FIX SUGGESTIONS:\n\n'; errorFields.forEach(([fieldName, result]) => { suggestions += `🔸 ${fieldName}:\n`; suggestions += ` Problem: ${result.message}\n`; // Add specific suggestions based on field type switch (result.fieldType) { case 'email': suggestions += ' 💡 Suggestions:\n'; suggestions += ' • Make sure it contains @ symbol\n'; suggestions += ' • Add domain like @gmail.com\n'; suggestions += ' • Remove any spaces\n'; suggestions += ' • Example: [email protected]\n\n'; break; case 'phone': suggestions += ' 💡 Suggestions:\n'; suggestions += ' • Use 10 digits for mobile numbers\n'; suggestions += ' • Remove country code if present\n'; suggestions += ' • Example: 9876543210\n\n'; break; case 'percentage': suggestions += ' 💡 Suggestions:\n'; suggestions += ' • Enter number between 0-100\n'; suggestions += ' • Don\'t include % symbol\n'; suggestions += ' • Use decimal for precision (e.g., 85.5)\n\n'; break; case 'year': suggestions += ' 💡 Suggestions:\n'; suggestions += ' • Enter 4-digit year (e.g., 2020)\n'; suggestions += ' • Year should be between 1900-2035\n\n'; break; case 'name': suggestions += ' 💡 Suggestions:\n'; suggestions += ' • Use only letters and spaces\n'; suggestions += ' • 2-50 characters long\n'; suggestions += ' • Example: John Doe\n\n'; break; case 'number': suggestions += ' 💡 Suggestions:\n'; suggestions += ' • Use only digits (0-9)\n'; suggestions += ' • No letters or special characters\n\n'; break; default: suggestions += ' 💡 Please check the format and try again\n\n'; } }); suggestions += '🔧 You can also try the "Auto-Fix" button to automatically correct common issues.'; alert(suggestions); } // Function to show validation errors with recovery options function showValidationErrors(validationResults) { const errorFields = Object.entries(validationResults.results) .filter(([_, result]) => !result.isValid); const errorList = errorFields .map(([fieldName, result]) => `• ${fieldName}: ${result.message}`) .join('\n'); ErrorHandler.showError('VALIDATION_ERROR', `Found ${errorFields.length} validation error${errorFields.length > 1 ? 's' : ''} in your form data.`, errorList, [ { text: '🔧 Auto-Fix Common Issues', icon: '🔧', primary: true, action: () => { autoFixValidationErrors(validationResults); } }, { text: '💡 Show Fix Suggestions', icon: '💡', action: () => { showValidationSuggestions(validationResults); } }, { text: 'Fix Errors Manually', icon: '✏️', action: () => { ErrorHandler.showNotification('Please correct the highlighted fields and try again', 'info'); } }, { text: 'Save Anyway (Skip Validation)', icon: '⚠️', action: () => { if (confirm('Are you sure you want to save data with validation errors? This may cause issues when filling forms.')) { // Force save without validation const inputs = document.querySelectorAll('input[data-key]'); const newTags = {}; inputs.forEach(input => { const tagName = input.getAttribute('data-key'); const value = input.value.trim(); newTags[tagName] = value; }); taggedData.tags = { ...taggedData.tags, ...newTags }; saveTaggedData(taggedData); ErrorHandler.showNotification('Data saved with validation warnings', 'warning'); } } }, { text: 'Reset to Defaults', icon: '🔄', action: () => { if (confirm('Reset all fields to default values? This will lose your current changes.')) { location.reload(); } } } ] ); console.error('Validation errors:', validationResults.results); } // Function to add visual validation feedback to inputs function addValidationFeedback(input, validation) { // Remove existing validation classes and icons input.classList.remove('validation-success', 'validation-error'); // Remove existing validation elements const existingIcon = input.parentNode.querySelector('.validation-icon'); const existingTooltip = input.parentNode.querySelector('.validation-tooltip'); if (existingIcon) existingIcon.remove(); if (existingTooltip) existingTooltip.remove(); if (!validation.isValid) { // Add error styling with subtle red border input.classList.add('validation-error'); input.style.borderColor = '#f44336'; input.style.boxShadow = '0 0 3px rgba(244, 67, 54, 0.2)'; // Add small error icon const errorIcon = document.createElement('div'); errorIcon.className = 'validation-icon error-icon'; errorIcon.style.cssText = ` position: absolute; right: 8px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; background: #f44336; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 10px; font-weight: bold; cursor: help; z-index: 1; `; errorIcon.textContent = '!'; errorIcon.title = validation.message; // Make parent position relative for absolute positioning input.parentNode.style.position = 'relative'; input.style.paddingRight = '32px'; // Make room for icon input.parentNode.appendChild(errorIcon); // Add hover tooltip for error message const tooltip = document.createElement('div'); tooltip.className = 'validation-tooltip'; tooltip.style.cssText = ` position: absolute; bottom: -35px; left: 0; right: 0; background: #f44336; color: white; padding: 6px 8px; border-radius: 4px; font-size: 11px; opacity: 0; pointer-events: none; transition: opacity 0.2s ease; z-index: 1000; box-shadow: 0 2px 8px rgba(0,0,0,0.2); `; tooltip.textContent = validation.message; // Show tooltip on hover errorIcon.addEventListener('mouseenter', () => { tooltip.style.opacity = '1'; }); errorIcon.addEventListener('mouseleave', () => { tooltip.style.opacity = '0'; }); input.parentNode.appendChild(tooltip); } else if (validation.fieldType) { // Add success styling with subtle green border input.classList.add('validation-success'); input.style.borderColor = '#4caf50'; input.style.boxShadow = '0 0 3px rgba(76, 175, 80, 0.2)'; // Add small success checkmark const successIcon = document.createElement('div'); successIcon.className = 'validation-icon success-icon'; successIcon.style.cssText = ` position: absolute; right: 8px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; background: #4caf50; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 10px; font-weight: bold; z-index: 1; `; successIcon.textContent = '✓'; successIcon.title = `Valid ${validation.fieldType}`; // Make parent position relative for absolute positioning input.parentNode.style.position = 'relative'; input.style.paddingRight = '32px'; // Make room for icon input.parentNode.appendChild(successIcon); } else { // Reset to default styling input.style.borderColor = '#ddd'; input.style.boxShadow = 'none'; input.style.paddingRight = '12px'; // Reset padding input.parentNode.style.position = 'static'; } } // Show backup/import options modal function showBackupImportModal() { // Create modal overlay const overlay = document.createElement('div'); overlay.id = 'backup-import-modal-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); z-index: 10000; display: flex; justify-content: center; align-items: center; font-family: Arial, sans-serif; `; // Create modal content const modal = document.createElement('div'); modal.style.cssText = ` background: white; border-radius: 16px; padding: 32px; max-width: 500px; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3); text-align: center; `; // Create title const title = document.createElement('h2'); title.style.cssText = 'margin: 0 0 24px 0; color: #333; font-size: 24px;'; title.textContent = '💾 Backup & Import Data'; // Create description const description = document.createElement('p'); description.style.cssText = 'color: #666; margin-bottom: 32px; line-height: 1.5;'; description.textContent = 'Export your form data for backup or import data from another device'; // Create button container const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = 'display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 24px;'; // Create export buttons const exportJSONBtn = document.createElement('button'); exportJSONBtn.textContent = '📁 Export JSON'; exportJSONBtn.style.cssText = ` background: #2196f3; color: white; border: none; padding: 16px; border-radius: 8px; font-size: 14px; font-weight: bold; cursor: pointer; transition: background 0.2s; `; exportJSONBtn.addEventListener('mouseenter', () => exportJSONBtn.style.background = '#1976d2'); exportJSONBtn.addEventListener('mouseleave', () => exportJSONBtn.style.background = '#2196f3'); exportJSONBtn.addEventListener('click', () => { exportDataAsJSON(); document.body.removeChild(overlay); }); const exportCSVBtn = document.createElement('button'); exportCSVBtn.textContent = '📊 Export CSV'; exportCSVBtn.style.cssText = ` background: #4caf50; color: white; border: none; padding: 16px; border-radius: 8px; font-size: 14px; font-weight: bold; cursor: pointer; transition: background 0.2s; `; exportCSVBtn.addEventListener('mouseenter', () => exportCSVBtn.style.background = '#45a049'); exportCSVBtn.addEventListener('mouseleave', () => exportCSVBtn.style.background = '#4caf50'); exportCSVBtn.addEventListener('click', () => { exportDataAsCSV(); document.body.removeChild(overlay); }); const importJSONBtn = document.createElement('button'); importJSONBtn.textContent = '📁 Import JSON'; importJSONBtn.style.cssText = ` background: #ff9800; color: white; border: none; padding: 16px; border-radius: 8px; font-size: 14px; font-weight: bold; cursor: pointer; transition: background 0.2s; `; importJSONBtn.addEventListener('mouseenter', () => importJSONBtn.style.background = '#f57c00'); importJSONBtn.addEventListener('mouseleave', () => importJSONBtn.style.background = '#ff9800'); importJSONBtn.addEventListener('click', () => { importDataFromJSON(); document.body.removeChild(overlay); }); const importCSVBtn = document.createElement('button'); importCSVBtn.textContent = '📊 Import CSV'; importCSVBtn.style.cssText = ` background: #9c27b0; color: white; border: none; padding: 16px; border-radius: 8px; font-size: 14px; font-weight: bold; cursor: pointer; transition: background 0.2s; `; importCSVBtn.addEventListener('mouseenter', () => importCSVBtn.style.background = '#7b1fa2'); importCSVBtn.addEventListener('mouseleave', () => importCSVBtn.style.background = '#9c27b0'); importCSVBtn.addEventListener('click', () => { importDataFromCSV(); document.body.removeChild(overlay); }); // Create close button const closeBtn = document.createElement('button'); closeBtn.textContent = '❌ Close'; closeBtn.style.cssText = ` background: #f44336; color: white; border: none; padding: 12px 24px; border-radius: 8px; font-size: 14px; font-weight: bold; cursor: pointer; transition: background 0.2s; `; closeBtn.addEventListener('mouseenter', () => closeBtn.style.background = '#da190b'); closeBtn.addEventListener('mouseleave', () => closeBtn.style.background = '#f44336'); closeBtn.addEventListener('click', () => document.body.removeChild(overlay)); // Add elements to modal buttonContainer.appendChild(exportJSONBtn); buttonContainer.appendChild(exportCSVBtn); buttonContainer.appendChild(importJSONBtn); buttonContainer.appendChild(importCSVBtn); modal.appendChild(title); modal.appendChild(description); modal.appendChild(buttonContainer); modal.appendChild(closeBtn); overlay.appendChild(modal); // Close on overlay click overlay.addEventListener('click', (e) => { if (e.target === overlay) { document.body.removeChild(overlay); } }); document.body.appendChild(overlay); } // Progress indicator and success animation functions function createProgressIndicator() { // Remove existing progress indicator if any const existing = document.getElementById('form-filler-progress'); if (existing) { existing.remove(); } // Create progress container const progressContainer = document.createElement('div'); progressContainer.id = 'form-filler-progress'; progressContainer.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10001; background: rgba(255, 255, 255, 0.95); border-radius: 12px; padding: 24px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); min-width: 300px; text-align: center; font-family: Arial, sans-serif; `; // Create title const title = document.createElement('div'); title.style.cssText = 'font-size: 18px; font-weight: bold; color: #333; margin-bottom: 16px;'; title.textContent = '🚀 Filling Form...'; // Create progress bar container const progressBarContainer = document.createElement('div'); progressBarContainer.style.cssText = ` width: 100%; height: 8px; background: #e0e0e0; border-radius: 4px; overflow: hidden; margin-bottom: 12px; `; // Create progress bar const progressBar = document.createElement('div'); progressBar.id = 'form-filler-progress-bar'; progressBar.style.cssText = ` width: 0%; height: 100%; background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 4px; transition: width 0.3s ease; `; // Create status text const statusText = document.createElement('div'); statusText.id = 'form-filler-status'; statusText.style.cssText = 'font-size: 14px; color: #666; margin-bottom: 8px;'; statusText.textContent = 'Initializing...'; // Create field counter const fieldCounter = document.createElement('div'); fieldCounter.id = 'form-filler-counter'; fieldCounter.style.cssText = 'font-size: 12px; color: #999;'; fieldCounter.textContent = '0 / 0 fields processed'; progressBarContainer.appendChild(progressBar); progressContainer.appendChild(title); progressContainer.appendChild(progressBarContainer); progressContainer.appendChild(statusText); progressContainer.appendChild(fieldCounter); document.body.appendChild(progressContainer); return { container: progressContainer, bar: progressBar, status: statusText, counter: fieldCounter, title: title }; } function updateProgress(current, total, fieldName, success) { const progressBar = document.getElementById('form-filler-progress-bar'); const statusText = document.getElementById('form-filler-status'); const fieldCounter = document.getElementById('form-filler-counter'); if (progressBar && statusText && fieldCounter) { const percentage = total > 0 ? (current / total) * 100 : 0; progressBar.style.width = `${percentage}%`; if (success) { statusText.textContent = `✅ Filled: ${fieldName}`; statusText.style.color = '#4caf50'; } else { statusText.textContent = `⚠️ Skipped: ${fieldName}`; statusText.style.color = '#ff9800'; } fieldCounter.textContent = `${current} / ${total} fields processed`; // Add pulse animation for successful fills if (success) { progressBar.style.boxShadow = '0 0 10px #4caf50'; setTimeout(() => { progressBar.style.boxShadow = 'none'; }, 300); } } } function showSuccessAnimation(filledCount, totalCount, skippedCount) { const progressContainer = document.getElementById('form-filler-progress'); if (!progressContainer) return; // Update to success state const title = progressContainer.querySelector('div'); const statusText = document.getElementById('form-filler-status'); const progressBar = document.getElementById('form-filler-progress-bar'); title.textContent = '🎉 Form Filling Complete!'; title.style.color = '#4caf50'; statusText.textContent = `✅ Successfully filled ${filledCount} fields`; statusText.style.color = '#4caf50'; statusText.style.fontWeight = 'bold'; progressBar.style.background = 'linear-gradient(90deg, #4caf50, #66bb6a)'; progressBar.style.boxShadow = '0 0 20px #4caf50'; // Add success animation progressContainer.style.animation = 'successPulse 0.6s ease-in-out'; // Add CSS animation if not already added if (!document.getElementById('success-animation-styles')) { const style = document.createElement('style'); style.id = 'success-animation-styles'; style.textContent = ` @keyframes successPulse { 0% { transform: translate(-50%, -50%) scale(1); } 50% { transform: translate(-50%, -50%) scale(1.05); } 100% { transform: translate(-50%, -50%) scale(1); } } @keyframes fadeOut { 0% { opacity: 1; transform: translate(-50%, -50%) scale(1); } 100% { opacity: 0; transform: translate(-50%, -50%) scale(0.9); } } `; document.head.appendChild(style); } // Show detailed results setTimeout(() => { const resultText = document.createElement('div'); resultText.style.cssText = 'font-size: 12px; color: #666; margin-top: 8px; line-height: 1.4;'; resultText.innerHTML = ` 📊 <strong>Results:</strong><br> ✅ Filled: ${filledCount} fields<br> ${skippedCount > 0 ? `⚠️ Skipped: ${skippedCount} fields<br>` : ''} 🎯 Total: ${totalCount} fields found `; progressContainer.appendChild(resultText); }, 500); // Auto-hide after 4 seconds with fade out animation setTimeout(() => { progressContainer.style.animation = 'fadeOut 0.5s ease-in-out forwards'; setTimeout(() => { if (progressContainer.parentNode) { progressContainer.parentNode.removeChild(progressContainer); } }, 500); }, 4000); } function hideProgressIndicator() { const progressContainer = document.getElementById('form-filler-progress'); if (progressContainer) { progressContainer.style.animation = 'fadeOut 0.5s ease-in-out forwards'; setTimeout(() => { if (progressContainer.parentNode) { progressContainer.parentNode.removeChild(progressContainer); } }, 500); } } // Function to generate automatic field mappings based on tag name function generateAutoFieldMappings(tagName) { const mappings = []; // Convert tag name to potential field names const cleanTagName = tagName.replace(/_/g, ' ').toLowerCase(); // Generate variations of the tag name const variations = [ // Original tag name as-is tagName, // Replace underscores with spaces tagName.replace(/_/g, ' '), // Title case version cleanTagName.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1) ).join(' '), // All lowercase with spaces cleanTagName, // All uppercase with spaces cleanTagName.toUpperCase(), // Camel case version cleanTagName.split(' ').map((word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1) ).join(' '), ]; // Add common variations based on tag patterns if (tagName.includes('COURSE')) { variations.push('Course', 'Preferred Course', 'Course Name', 'Course Selection'); } if (tagName.includes('SUBJECT')) { variations.push('Subject', 'Subject Name', 'Preferred Subject'); } if (tagName.includes('DEPARTMENT')) { variations.push('Department', 'Dept', 'Department Name'); } if (tagName.includes('COLLEGE')) { variations.push('College', 'College Name', 'Institution'); } if (tagName.includes('UNIVERSITY')) { variations.push('University', 'University Name'); } if (tagName.includes('ADDRESS')) { variations.push('Address', 'Home Address', 'Permanent Address', 'Current Address'); } if (tagName.includes('CITY')) { variations.push('City', 'City Name', 'Home City'); } if (tagName.includes('STATE')) { variations.push('State', 'State Name', 'Home State'); } if (tagName.includes('COUNTRY')) { variations.push('Country', 'Country Name', 'Home Country'); } if (tagName.includes('FATHER')) { variations.push('Father Name', 'Father\'s Name', 'Father'); } if (tagName.includes('MOTHER')) { variations.push('Mother Name', 'Mother\'s Name', 'Mother'); } if (tagName.includes('GUARDIAN')) { variations.push('Guardian Name', 'Guardian\'s Name', 'Guardian'); } if (tagName.includes('HOBBY') || tagName.includes('HOBBIES')) { variations.push('Hobbies', 'Hobby', 'Interests', 'Interest'); } if (tagName.includes('SKILL') || tagName.includes('SKILLS')) { variations.push('Skills', 'Skill', 'Technical Skills', 'Key Skills'); } if (tagName.includes('EXPERIENCE')) { variations.push('Experience', 'Work Experience', 'Previous Experience'); } if (tagName.includes('QUALIFICATION')) { variations.push('Qualification', 'Qualifications', 'Educational Qualification'); } // Remove duplicates and empty values const uniqueVariations = [...new Set(variations)].filter(v => v && v.trim()); // Add all unique variations as potential field mappings uniqueVariations.forEach(variation => { if (variation !== tagName) { // Don't map to itself mappings.push(variation); } }); console.log(`🔗 Generated ${mappings.length} auto-mappings for tag "${tagName}":`, mappings); return mappings; } // Function to get value for a field using tag system function getValueForField(titleText) { // Clean the title text (remove asterisks, trim whitespace) const cleanTitle = titleText.replace(/\s*\*\s*$/, '').trim(); // Check if we have a direct field mapping const tagName = taggedData.fieldMappings[cleanTitle]; if (tagName && taggedData.tags[tagName]) { return taggedData.tags[tagName]; } // Check for case-insensitive field mapping for (const [fieldName, tag] of Object.entries(taggedData.fieldMappings)) { if (fieldName.toLowerCase() === cleanTitle.toLowerCase()) { return taggedData.tags[tag] || null; } } return null; } // Function to show customization modal function showCustomizationModal() { return new Promise((resolve) => { // Create modal overlay const overlay = document.createElement('div'); overlay.id = 'form-filler-modal-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); z-index: 10000; display: flex; justify-content: center; align-items: center; font-family: Arial, sans-serif; `; // Create modal content const modal = document.createElement('div'); modal.style.cssText = ` background: white; border-radius: 12px; padding: 24px; max-width: 600px; max-height: 80vh; overflow-y: auto; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); position: relative; `; // Create modal content using DOM methods (safer than innerHTML) // Create title const title = document.createElement('h2'); title.style.cssText = 'margin: 0 0 20px 0; color: #333; text-align: center;'; title.textContent = '🎯 Customize Your Form Data'; modal.appendChild(title); // Create description const description = document.createElement('p'); description.style.cssText = 'color: #666; text-align: center; margin-bottom: 20px;'; description.textContent = 'Edit the values below to match your information'; modal.appendChild(description); // Create form const form = document.createElement('form'); form.id = 'customization-form'; form.style.cssText = 'display: grid; gap: 12px;'; // Function to create a field row function createFieldRow(key, value, isCustom = false) { const fieldId = `field-${key.replace(/[^a-zA-Z0-9]/g, '-')}`; // Create field container const fieldDiv = document.createElement('div'); fieldDiv.style.cssText = `display: grid; grid-template-columns: 1fr 2fr ${isCustom ? 'auto' : ''}; gap: 8px; align-items: center;`; fieldDiv.setAttribute('data-field-key', key); // Create label const label = document.createElement('label'); label.setAttribute('for', fieldId); label.style.cssText = 'font-weight: bold; color: #333; font-size: 14px;'; label.textContent = `${key}:`; // Create input const input = document.createElement('input'); input.type = 'text'; input.id = fieldId; input.value = value; input.setAttribute('data-key', key); input.style.cssText = ` padding: 8px 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border-color 0.2s; `; // Add focus/blur events with validation input.addEventListener('focus', () => { input.style.borderColor = '#2196f3'; input.style.boxShadow = '0 0 0 2px rgba(33, 150, 243, 0.1)'; // Hide tooltip on focus const tooltip = input.parentNode.querySelector('.validation-tooltip'); if (tooltip) { tooltip.style.opacity = '0'; } }); input.addEventListener('blur', () => { // Validate field on blur const validation = validateField(key, input.value); addValidationFeedback(input, validation); }); // Add real-time validation on input input.addEventListener('input', () => { // Clear previous validation styling during typing input.classList.remove('validation-success', 'validation-error'); // Remove validation icons while typing const existingIcon = input.parentNode.querySelector('.validation-icon'); const existingTooltip = input.parentNode.querySelector('.validation-tooltip'); if (existingIcon) existingIcon.remove(); if (existingTooltip) existingTooltip.remove(); // Reset padding and styling input.style.paddingRight = '12px'; input.style.borderColor = '#2196f3'; input.style.boxShadow = '0 0 0 2px rgba(33, 150, 243, 0.1)'; }); fieldDiv.appendChild(label); fieldDiv.appendChild(input); // Add delete button for custom fields if (isCustom) { const deleteBtn = document.createElement('button'); deleteBtn.type = 'button'; deleteBtn.textContent = '🗑️'; deleteBtn.title = 'Delete this field'; deleteBtn.style.cssText = ` background: #f44336; color: white; border: none; padding: 8px; border-radius: 4px; cursor: pointer; font-size: 12px; transition: background 0.2s; `; deleteBtn.addEventListener('mouseenter', () => deleteBtn.style.background = '#da190b'); deleteBtn.addEventListener('mouseleave', () => deleteBtn.style.background = '#f44336'); deleteBtn.addEventListener('click', () => { // Confirm deletion const confirmMessage = `Are you sure you want to delete the tag "${key}"?\n\nThis will also remove all field mappings associated with this tag.`; if (confirm(confirmMessage)) { // Remove the tag from taggedData delete taggedData.tags[key]; // Remove all field mappings that use this tag const removedMappings = []; Object.keys(taggedData.fieldMappings).forEach(fieldName => { if (taggedData.fieldMappings[fieldName] === key) { delete taggedData.fieldMappings[fieldName]; removedMappings.push(fieldName); } }); // Save updated data to localStorage saveTaggedData(taggedData); // Remove the field from UI fieldDiv.remove(); // Update the tag selector dropdown updateTagSelector(); // Update mappings display if it exists if (typeof updateMappingsDisplay === 'function') { updateMappingsDisplay(); } // Show success message let successMessage = `✅ Deleted tag "${key}" successfully!`; if (removedMappings.length > 0) { successMessage += `\n\n🔗 Also removed ${removedMappings.length} field mapping${removedMappings.length > 1 ? 's' : ''}:`; successMessage += `\n${removedMappings.map(field => `• "${field}"`).join('\n')}`; } alert(successMessage); console.log(`🗑️ Deleted tag "${key}" and ${removedMappings.length} associated field mappings`); console.log('Removed field mappings:', removedMappings); } }); fieldDiv.appendChild(deleteBtn); } return fieldDiv; } // Add tag fields (show tags, not individual field mappings) Object.entries(taggedData.tags).forEach(([tagName, value]) => { const isCustomField = !defaultTaggedData.tags.hasOwnProperty(tagName); const fieldRow = createFieldRow(tagName, value, isCustomField); form.appendChild(fieldRow); }); modal.appendChild(form); // Create "Add Custom Tag" section const addFieldSection = document.createElement('div'); addFieldSection.style.cssText = 'margin-top: 16px; padding-top: 16px; border-top: 2px solid #eee;'; const addFieldTitle = document.createElement('h3'); addFieldTitle.style.cssText = 'margin: 0 0 12px 0; color: #333; font-size: 16px; text-align: center;'; addFieldTitle.textContent = '🏷️ Add Custom Tag'; // Create two sections: Add New Tag and Map Field to Existing Tag // Section 1: Add New Tag const newTagSection = document.createElement('div'); newTagSection.style.cssText = 'margin-bottom: 16px; padding: 12px; background: #f8f9fa; border-radius: 8px;'; const newTagLabel = document.createElement('h4'); newTagLabel.style.cssText = 'margin: 0 0 8px 0; color: #333; font-size: 14px;'; newTagLabel.textContent = '➕ Create New Tag'; const newTagContainer = document.createElement('div'); newTagContainer.style.cssText = 'display: grid; grid-template-columns: 1fr 1fr auto; gap: 8px; align-items: center;'; // Tag name input const tagNameInput = document.createElement('input'); tagNameInput.type = 'text'; tagNameInput.placeholder = 'Tag Name (e.g., "FATHER_NAME")'; tagNameInput.style.cssText = ` padding: 8px 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border-color 0.2s; text-transform: uppercase; `; tagNameInput.addEventListener('focus', () => tagNameInput.style.borderColor = '#4caf50'); tagNameInput.addEventListener('blur', () => tagNameInput.style.borderColor = '#ddd'); tagNameInput.addEventListener('input', (e) => { e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, '_'); }); // Tag value input const tagValueInput = document.createElement('input'); tagValueInput.type = 'text'; tagValueInput.placeholder = 'Tag Value (e.g., "John Doe")'; tagValueInput.style.cssText = ` padding: 8px 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border-color 0.2s; `; tagValueInput.addEventListener('focus', () => tagValueInput.style.borderColor = '#4caf50'); tagValueInput.addEventListener('blur', () => tagValueInput.style.borderColor = '#ddd'); // Add new tag button const addNewTagButton = document.createElement('button'); addNewTagButton.type = 'button'; addNewTagButton.textContent = '➕'; addNewTagButton.title = 'Add new tag'; addNewTagButton.style.cssText = ` background: #4caf50; color: white; border: none; padding: 10px 12px; border-radius: 6px; cursor: pointer; font-size: 16px; transition: background 0.2s; `; addNewTagButton.addEventListener('mouseenter', () => addNewTagButton.style.background = '#45a049'); addNewTagButton.addEventListener('mouseleave', () => addNewTagButton.style.background = '#4caf50'); newTagContainer.appendChild(tagNameInput); newTagContainer.appendChild(tagValueInput); newTagContainer.appendChild(addNewTagButton); newTagSection.appendChild(newTagLabel); newTagSection.appendChild(newTagContainer); // Section 2: Map Field to Existing Tag const mapFieldSection = document.createElement('div'); mapFieldSection.style.cssText = 'padding: 12px; background: #e8f5e8; border-radius: 8px;'; const mapFieldLabel = document.createElement('h4'); mapFieldLabel.style.cssText = 'margin: 0 0 8px 0; color: #333; font-size: 14px;'; mapFieldLabel.textContent = '🔗 Map Field to Existing Tag'; const mapFieldContainer = document.createElement('div'); mapFieldContainer.style.cssText = 'display: grid; grid-template-columns: 1fr 1fr auto; gap: 8px; align-items: center;'; // Field name input const fieldNameInput = document.createElement('input'); fieldNameInput.type = 'text'; fieldNameInput.placeholder = 'Field Name (e.g., "Father Name")'; fieldNameInput.style.cssText = ` padding: 8px 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border-color 0.2s; `; fieldNameInput.addEventListener('focus', () => fieldNameInput.style.borderColor = '#4caf50'); fieldNameInput.addEventListener('blur', () => fieldNameInput.style.borderColor = '#ddd'); // Tag selector dropdown const tagSelector = document.createElement('select'); tagSelector.style.cssText = ` padding: 8px 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border-color 0.2s; background: white; max-width: 100%; font-family: 'Courier New', monospace; line-height: 1.4; `; tagSelector.addEventListener('focus', () => tagSelector.style.borderColor = '#4caf50'); tagSelector.addEventListener('blur', () => tagSelector.style.borderColor = '#ddd'); // Function to truncate long text for better display function truncateText(text, maxLength = 20) { if (!text) return ''; if (text.length <= maxLength) return text; return text.substring(0, maxLength) + '...'; } // Populate tag selector function updateTagSelector() { // Clear existing options using DOM methods (safer than innerHTML) while (tagSelector.firstChild) { tagSelector.removeChild(tagSelector.firstChild); } const defaultOption = document.createElement('option'); defaultOption.value = ''; defaultOption.textContent = 'Select existing tag...'; tagSelector.appendChild(defaultOption); // Sort tags alphabetically for better organization const sortedTagNames = Object.keys(taggedData.tags).sort(); sortedTagNames.forEach(tagName => { const option = document.createElement('option'); option.value = tagName; const tagValue = taggedData.tags[tagName]; const truncatedValue = truncateText(tagValue, 20); // Format: TAG_NAME: Short Value option.textContent = `${tagName}: ${truncatedValue}`; // Add full value as title for tooltip option.title = `${tagName}\nFull value: ${tagValue}`; tagSelector.appendChild(option); }); } updateTagSelector(); // Add field mapping button const addMappingButton = document.createElement('button'); addMappingButton.type = 'button'; addMappingButton.textContent = '🔗'; addMappingButton.title = 'Map field to tag'; addMappingButton.style.cssText = ` background: #2196f3; color: white; border: none; padding: 10px 12px; border-radius: 6px; cursor: pointer; font-size: 16px; transition: background 0.2s; `; addMappingButton.addEventListener('mouseenter', () => addMappingButton.style.background = '#1976d2'); addMappingButton.addEventListener('mouseleave', () => addMappingButton.style.background = '#2196f3'); mapFieldContainer.appendChild(fieldNameInput); mapFieldContainer.appendChild(tagSelector); mapFieldContainer.appendChild(addMappingButton); mapFieldSection.appendChild(mapFieldLabel); mapFieldSection.appendChild(mapFieldContainer); // Add button const addButton = document.createElement('button'); addButton.type = 'button'; addButton.textContent = '➕'; addButton.title = 'Add this field'; addButton.style.cssText = ` background: #4caf50; color: white; border: none; padding: 10px 12px; border-radius: 6px; cursor: pointer; font-size: 16px; transition: background 0.2s; `; addButton.addEventListener('mouseenter', () => addButton.style.background = '#45a049'); addButton.addEventListener('mouseleave', () => addButton.style.background = '#4caf50'); // Add new tag functionality function addNewTag() { const tagName = tagNameInput.value.trim(); const tagValue = tagValueInput.value.trim(); if (!tagName) { alert('Please enter a tag name!'); tagNameInput.focus(); return; } if (!tagValue) { alert('Please enter a tag value!'); tagValueInput.focus(); return; } // Check if tag already exists if (taggedData.tags[tagName]) { alert('A tag with this name already exists! Use the mapping section below to assign fields to it.'); tagNameInput.focus(); return; } // Add the new tag taggedData.tags[tagName] = tagValue; // Auto-create intelligent field mappings based on tag name const autoMappings = generateAutoFieldMappings(tagName); autoMappings.forEach(fieldName => { taggedData.fieldMappings[fieldName] = tagName; }); // Create and add the new tag field to the form const newFieldRow = createFieldRow(tagName, tagValue, true); form.appendChild(newFieldRow); // Update the tag selector dropdown updateTagSelector(); // Update mappings display if it exists if (typeof updateMappingsDisplay === 'function') { updateMappingsDisplay(); } // Clear inputs tagNameInput.value = ''; tagValueInput.value = ''; tagNameInput.focus(); // Show success message with auto-mappings info let successMessage = `✅ Added new tag: ${tagName} = ${tagValue}`; if (autoMappings.length > 0) { successMessage += `\n\n🔗 Auto-created ${autoMappings.length} field mapping${autoMappings.length > 1 ? 's' : ''}:`; successMessage += `\n${autoMappings.map(field => `• "${field}"`).join('\n')}`; successMessage += `\n\n💡 These fields will now automatically use the value "${tagValue}" when filling forms.`; successMessage += `\n\nYou can add more field mappings using the "Map Field to Existing Tag" section below.`; } else { successMessage += `\n\n💡 Use the "Map Field to Existing Tag" section below to map form fields to this tag.`; } alert(successMessage); console.log(`✅ Added new tag: ${tagName} = ${tagValue}`); if (autoMappings.length > 0) { console.log(`🔗 Auto-mapped to fields:`, autoMappings); } } // Add field mapping functionality function addFieldMapping() { const fieldName = fieldNameInput.value.trim(); const selectedTag = tagSelector.value; if (!fieldName) { alert('Please enter a field name!'); fieldNameInput.focus(); return; } if (!selectedTag) { alert('Please select a tag to map to!'); tagSelector.focus(); return; } // Add the field mapping taggedData.fieldMappings[fieldName] = selectedTag; // Update the mappings display updateMappingsDisplay(); // Clear inputs fieldNameInput.value = ''; tagSelector.value = ''; fieldNameInput.focus(); // Show success message const tagValue = taggedData.tags[selectedTag]; alert(`✅ Mapped "${fieldName}" to tag "${selectedTag}" (${tagValue})`); console.log(`✅ Mapped field "${fieldName}" to tag "${selectedTag}"`); } addNewTagButton.addEventListener('click', addNewTag); addMappingButton.addEventListener('click', addFieldMapping); // Allow Enter key navigation tagNameInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); tagValueInput.focus(); } }); tagValueInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); addNewTag(); } }); fieldNameInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); tagSelector.focus(); } }); tagSelector.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); addFieldMapping(); } }); // Section 3: View Current Mappings const viewMappingsSection = document.createElement('div'); viewMappingsSection.style.cssText = 'margin-top: 16px; padding: 12px; background: #fff3e0; border-radius: 8px; border: 1px solid #ffcc02;'; const viewMappingsLabel = document.createElement('h4'); viewMappingsLabel.style.cssText = 'margin: 0 0 8px 0; color: #333; font-size: 14px;'; viewMappingsLabel.textContent = '📋 Current Field Mappings'; const mappingsContainer = document.createElement('div'); mappingsContainer.style.cssText = 'max-height: 200px; overflow-y: auto; font-size: 12px;'; // Function to update mappings display function updateMappingsDisplay() { // Clear existing content while (mappingsContainer.firstChild) { mappingsContainer.removeChild(mappingsContainer.firstChild); } // Group field mappings by tag const tagToFields = {}; Object.entries(taggedData.fieldMappings).forEach(([fieldName, tagName]) => { if (!tagToFields[tagName]) { tagToFields[tagName] = []; } tagToFields[tagName].push(fieldName); }); // Display each tag and its mapped fields Object.entries(tagToFields).forEach(([tagName, fields]) => { const tagValue = taggedData.tags[tagName] || 'No value set'; const mappingRow = document.createElement('div'); mappingRow.style.cssText = 'margin-bottom: 8px; padding: 8px; background: white; border-radius: 4px; border-left: 4px solid #4caf50;'; const tagInfo = document.createElement('div'); tagInfo.style.cssText = 'font-weight: bold; color: #2e7d32; margin-bottom: 4px;'; tagInfo.textContent = `🏷️ ${tagName}: "${tagValue}"`; const fieldsInfo = document.createElement('div'); fieldsInfo.style.cssText = 'color: #666; font-size: 11px; margin-left: 16px;'; fieldsInfo.textContent = `📝 Fields: ${fields.join(', ')}`; mappingRow.appendChild(tagInfo); mappingRow.appendChild(fieldsInfo); mappingsContainer.appendChild(mappingRow); }); // Show message if no mappings exist if (Object.keys(tagToFields).length === 0) { const noMappings = document.createElement('div'); noMappings.style.cssText = 'text-align: center; color: #999; font-style: italic; padding: 16px;'; noMappings.textContent = 'No field mappings configured yet'; mappingsContainer.appendChild(noMappings); } } // Initial display updateMappingsDisplay(); viewMappingsSection.appendChild(viewMappingsLabel); viewMappingsSection.appendChild(mappingsContainer); addFieldSection.appendChild(addFieldTitle); addFieldSection.appendChild(newTagSection); addFieldSection.appendChild(mapFieldSection); addFieldSection.appendChild(viewMappingsSection); // Add comprehensive tip const customFieldTip = document.createElement('div'); customFieldTip.style.cssText = 'text-align: center; font-size: 12px; color: #666; margin-top: 12px; line-height: 1.4;'; customFieldTip.textContent = '💡 Tags are reusable values. Multiple field names can use the same tag. All data is saved permanently in your browser.'; addFieldSection.appendChild(customFieldTip); modal.appendChild(addFieldSection); // Create validation summary section const validationSummary = document.createElement('div'); validationSummary.id = 'validation-summary'; validationSummary.style.cssText = ` margin-top: 16px; padding: 8px 12px; border-radius: 6px; display: none; font-size: 12px; text-align: center; font-weight: 500; transition: all 0.3s ease; `; modal.appendChild(validationSummary); // Function to update validation summary function updateValidationSummary() { const inputs = modal.querySelectorAll('input[data-key]'); const validationResults = {}; let validCount = 0; let errorCount = 0; let totalValidated = 0; inputs.forEach(input => { const tagName = input.getAttribute('data-key'); const value = input.value.trim(); if (value) { // Only validate non-empty fields const validation = validateField(tagName, value); validationResults[tagName] = validation; totalValidated++; if (validation.fieldType) { // Field has validation rules if (validation.isValid) { validCount++; } else { errorCount++; } } } }); if (totalValidated > 0) { validationSummary.style.display = 'block'; if (errorCount > 0) { validationSummary.style.background = 'rgba(244, 67, 54, 0.1)'; validationSummary.style.border = '1px solid rgba(244, 67, 54, 0.3)'; validationSummary.style.color = '#d32f2f'; validationSummary.innerHTML = `<span style="font-size: 14px;">⚠️</span> ${errorCount} field${errorCount > 1 ? 's need' : ' needs'} attention`; } else if (validCount > 0) { validationSummary.style.background = 'rgba(76, 175, 80, 0.1)'; validationSummary.style.border = '1px solid rgba(76, 175, 80, 0.3)'; validationSummary.style.color = '#388e3c'; validationSummary.innerHTML = `<span style="font-size: 14px;">✅</span> All fields validated`; } else { validationSummary.style.display = 'none'; } } else { validationSummary.style.display = 'none'; } } // Add validation summary update to input events const originalCreateFieldRow = createFieldRow; function createFieldRowWithValidation(key, value, isCustom = false) { const fieldRow = originalCreateFieldRow(key, value, isCustom); const input = fieldRow.querySelector('input'); if (input) { // Add validation summary update to existing events input.addEventListener('blur', () => { setTimeout(updateValidationSummary, 100); // Small delay to ensure validation feedback is applied }); input.addEventListener('input', () => { setTimeout(updateValidationSummary, 100); }); } return fieldRow; } // Replace createFieldRow with validation-aware version createFieldRow = createFieldRowWithValidation; // Create button container const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = 'display: flex; gap: 12px; justify-content: center; margin-top: 24px; flex-wrap: wrap;'; // Create save only button const saveOnlyButton = document.createElement('button'); saveOnlyButton.id = 'save-only'; saveOnlyButton.textContent = '💾 Save'; saveOnlyButton.style.cssText = ` background: #2196f3; color: white; border: none; padding: 12px 20px; border-radius: 6px; font-size: 14px; font-weight: bold; cursor: pointer; transition: background 0.2s; `; saveOnlyButton.addEventListener('mouseenter', () => saveOnlyButton.style.background = '#1976d2'); saveOnlyButton.addEventListener('mouseleave', () => saveOnlyButton.style.background = '#2196f3'); // Create save and fill button const saveAndFillButton = document.createElement('button'); saveAndFillButton.id = 'save-and-fill'; saveAndFillButton.textContent = '🚀 Fill Form'; saveAndFillButton.style.cssText = ` background: #4caf50; color: white; border: none; padding: 12px 20px; border-radius: 6px; font-size: 14px; font-weight: bold; cursor: pointer; transition: background 0.2s; `; saveAndFillButton.addEventListener('mouseenter', () => saveAndFillButton.style.background = '#45a049'); saveAndFillButton.addEventListener('mouseleave', () => saveAndFillButton.style.background = '#4caf50'); // Create cancel button const cancelButton = document.createElement('button'); cancelButton.id = 'cancel-modal'; cancelButton.textContent = '❌ Cancel'; cancelButton.style.cssText = ` background: #f44336; color: white; border: none; padding: 12px 20px; border-radius: 6px; font-size: 14px; font-weight: bold; cursor: pointer; transition: background 0.2s; `; cancelButton.addEventListener('mouseenter', () => cancelButton.style.background = '#da190b'); cancelButton.addEventListener('mouseleave', () => cancelButton.style.background = '#f44336'); buttonContainer.appendChild(saveOnlyButton); buttonContainer.appendChild(saveAndFillButton); buttonContainer.appendChild(cancelButton); modal.appendChild(buttonContainer); // Create tip text const tip = document.createElement('div'); tip.style.cssText = 'text-align: center; margin-top: 16px; font-size: 12px; color: #666; line-height: 1.4;'; tip.textContent = '💡 Save: Store your data for later use | Fill Form: Save data and immediately fill the current form'; modal.appendChild(tip); overlay.appendChild(modal); document.body.appendChild(overlay); // Mark modal as open isCustomizationModalOpen = true; // Function to close modal and update state function closeModal() { if (document.body.contains(overlay)) { document.body.removeChild(overlay); } isCustomizationModalOpen = false; } // Function to collect and save data with validation function collectAndSaveData() { // Collect all tag values const inputs = modal.querySelectorAll('input[data-key]'); const newTags = {}; inputs.forEach(input => { const tagName = input.getAttribute('data-key'); const value = input.value.trim(); newTags[tagName] = value; }); // Validate all collected data const validationResults = validateAllData(newTags); if (validationResults.hasErrors) { // Show validation errors and prevent saving showValidationErrors(validationResults); // Add visual feedback to invalid inputs inputs.forEach(input => { const tagName = input.getAttribute('data-key'); const validation = validationResults.results[tagName]; if (validation) { addValidationFeedback(input, validation); } }); return false; // Prevent saving } // Update global tagged data taggedData.tags = { ...taggedData.tags, ...newTags }; // Save to localStorage saveTaggedData(taggedData); console.log('✅ Tagged data validated and saved to localStorage!'); return true; // Success } // Add event listeners for save only button saveOnlyButton.addEventListener('click', () => { const isValid = collectAndSaveData(); if (isValid) { // Close modal closeModal(); // Show success message alert('💾 Data validated and saved successfully! You can now fill forms with your saved data.'); // Resolve with false (don't fill form) resolve(false); } // If validation fails, modal stays open for corrections }); // Add event listeners for save and fill button saveAndFillButton.addEventListener('click', () => { const isValid = collectAndSaveData(); if (isValid) { // Close modal closeModal(); // Resolve with true (proceed to fill form) resolve(true); } // If validation fails, modal stays open for corrections }); cancelButton.addEventListener('click', () => { closeModal(); resolve(false); }); // Close on overlay click overlay.addEventListener('click', (e) => { if (e.target === overlay) { closeModal(); resolve(false); } }); // Focus first input setTimeout(() => { const firstInput = modal.querySelector('input'); if (firstInput) firstInput.focus(); }, 100); }); } // Function to fill text inputs function fillTextInput(input, value) { if (!input || !value) return false; // Set the value input.value = value; // Trigger events to ensure Google Forms recognizes the input const events = ['input', 'change', 'blur']; events.forEach(eventType => { const event = new Event(eventType, { bubbles: true }); input.dispatchEvent(event); }); return true; } // Function to fill date inputs function fillDateInput(input, value) { if (!input || !value) return false; try { // Convert various date formats to YYYY-MM-DD format let dateValue = value; // If value is already in YYYY-MM-DD format, use it directly if (/^\d{4}-\d{2}-\d{2}$/.test(value)) { dateValue = value; } // If value is in DD/MM/YYYY or DD-MM-YYYY format else if (/^\d{1,2}[\/\-]\d{1,2}[\/\-]\d{4}$/.test(value)) { const parts = value.split(/[\/\-]/); dateValue = `${parts[2]}-${parts[1].padStart(2, '0')}-${parts[0].padStart(2, '0')}`; } // If value is in MM/DD/YYYY or MM-DD-YYYY format else if (/^\d{1,2}[\/\-]\d{1,2}[\/\-]\d{4}$/.test(value)) { const parts = value.split(/[\/\-]/); dateValue = `${parts[2]}-${parts[0].padStart(2, '0')}-${parts[1].padStart(2, '0')}`; } // If value is just a year (for YOP fields) else if (/^\d{4}$/.test(value)) { // Use January 1st of that year as default dateValue = `${value}-01-01`; } // Try to parse as a date string else { const date = new Date(value); if (!isNaN(date.getTime())) { dateValue = date.toISOString().split('T')[0]; } else { console.log(`Could not parse date value: ${value}`); return false; } } // Set the date value input.value = dateValue; input.setAttribute('data-initial-value', dateValue); // Trigger events to ensure Google Forms recognizes the input const events = ['input', 'change', 'blur']; events.forEach(eventType => { const event = new Event(eventType, { bubbles: true }); input.dispatchEvent(event); }); console.log(`Date input filled: ${value} → ${dateValue}`); return true; } catch (error) { ErrorHandler.showNotification(`Failed to fill date field: ${error.message}`, 'error'); console.error(`Error filling date input:`, error); return false; } } // Function to fill textarea inputs function fillTextarea(textarea, value) { if (!textarea || !value) return false; // Set the value textarea.value = value; textarea.setAttribute('data-initial-value', value); // Trigger events to ensure Google Forms recognizes the input const events = ['input', 'change', 'blur']; events.forEach(eventType => { const event = new Event(eventType, { bubbles: true }); textarea.dispatchEvent(event); }); return true; } // Function to select radio button function selectRadioButton(container, value) { if (!container || !value) return false; try { console.log(`🔘 Looking for radio button with value: ${value}`); // Find all radio button options const radioOptions = container.querySelectorAll('[role="radio"]'); console.log(`Found ${radioOptions.length} radio options`); let targetOption = null; // Look for exact match by data-value or aria-label for (const option of radioOptions) { const dataValue = option.getAttribute('data-value'); const ariaLabel = option.getAttribute('aria-label'); const textElement = option.querySelector('.aDTYNe'); const textValue = textElement ? textElement.textContent.trim() : ''; console.log(`Radio option - data-value: "${dataValue}", aria-label: "${ariaLabel}", text: "${textValue}"`); // Check data-value, aria-label, and text content for matches if ((dataValue && dataValue.toLowerCase() === value.toLowerCase()) || (ariaLabel && ariaLabel.toLowerCase() === value.toLowerCase()) || (textValue && textValue.toLowerCase() === value.toLowerCase())) { targetOption = option; console.log(`✅ Found matching radio option: ${dataValue || ariaLabel || textValue}`); break; } } if (targetOption) { // Click the radio button to select it targetOption.click(); // Add visual highlighting // targetOption.style.cssText += ` // background: linear-gradient(45deg, #2196f3, #64b5f6) !important; // border: 2px solid #1976d2 !important; // box-shadow: 0 0 10px #2196f3 !important; // transform: scale(1.02) !important; // `; console.log(`✅ Radio button selected: ${value}`); return true; } else { console.log(`❌ No matching radio option found for: ${value}`); return false; } } catch (error) { ErrorHandler.showNotification(`Failed to select radio button: ${value}`, 'error'); console.error(`Error selecting radio button:`, error); return false; } } // Function to select checkboxes function selectCheckboxes(container, value) { if (!container || !value) return false; try { console.log(`☑️ Looking for checkboxes with value: ${value}`); // Split value by comma for multiple selections const values = value.split(',').map(v => v.trim()); console.log(`Checkbox values to select:`, values); // Find all checkbox options const checkboxOptions = container.querySelectorAll('[role="checkbox"]'); console.log(`Found ${checkboxOptions.length} checkbox options`); let selectedCount = 0; values.forEach(targetValue => { let targetOption = null; // Look for exact match by data-answer-value or aria-label for (const option of checkboxOptions) { const dataValue = option.getAttribute('data-answer-value'); const ariaLabel = option.getAttribute('aria-label'); const textElement = option.querySelector('.aDTYNe'); const textValue = textElement ? textElement.textContent.trim() : ''; console.log(`Checkbox option - data-answer-value: "${dataValue}", aria-label: "${ariaLabel}", text: "${textValue}"`); // Check data-answer-value, aria-label, and text content for matches if ((dataValue && dataValue.toLowerCase() === targetValue.toLowerCase()) || (ariaLabel && ariaLabel.toLowerCase() === targetValue.toLowerCase()) || (textValue && textValue.toLowerCase() === targetValue.toLowerCase())) { targetOption = option; console.log(`✅ Found matching checkbox option: ${dataValue || ariaLabel || textValue}`); break; } } if (targetOption) { // Check if already selected const isChecked = targetOption.getAttribute('aria-checked') === 'true'; if (!isChecked) { // Click the checkbox to select it targetOption.click(); selectedCount++; // Add visual highlighting // targetOption.style.cssText += ` // background: linear-gradient(45deg, #4caf50, #81c784) !important; // border: 2px solid #388e3c !important; // box-shadow: 0 0 10px #4caf50 !important; // transform: scale(1.02) !important; // `; console.log(`✅ Checkbox selected: ${targetValue}`); } else { console.log(`ℹ️ Checkbox already selected: ${targetValue}`); selectedCount++; } } else { console.log(`❌ No matching checkbox found for: ${targetValue}`); } }); return selectedCount > 0; } catch (error) { ErrorHandler.showNotification(`Failed to select checkboxes: ${value}`, 'error'); console.error(`Error selecting checkboxes:`, error); return false; } } // Highlight dropdown options for manual selection function selectDropdownOption(container, value) { if (!container || !value) return false; return new Promise((resolve) => { try { console.log(`🔍 Highlighting dropdown option for manual selection: ${value}`); // Find all options with data-value attribute const allOptions = container.querySelectorAll('[role="option"][data-value]'); console.log(`Found ${allOptions.length} options with data-value`); let targetOption = null; // Look for exact match by data-value or text content for (const option of allOptions) { const dataValue = option.getAttribute('data-value'); const textElement = option.querySelector('.vRMGwf.oJeWuf'); const textValue = textElement ? textElement.textContent.trim() : ''; console.log(`Option - data-value: "${dataValue}", text: "${textValue}"`); // Check both data-value and text content for matches if ((dataValue && dataValue.toLowerCase() === value.toLowerCase()) || (textValue && textValue.toLowerCase() === value.toLowerCase() && textValue.toLowerCase() !== 'choose')) { targetOption = option; console.log(`✅ Found matching option: data-value="${dataValue}", text="${textValue}"`); break; } } if (targetOption) { // Highlight the option for manual selection console.log(`🎯 Highlighting "${value}" option for manual selection`); // Add visual highlighting with a distinctive green style const originalStyle = targetOption.style.cssText; targetOption.style.cssText += ` background: linear-gradient(45deg, #4caf50, #8bc34a) !important; border: 3px solid #2e7d32 !important; box-shadow: 0 0 15px #4caf50, inset 0 0 10px rgba(76,175,80,0.3) !important; transform: scale(1.02) !important; z-index: 9999 !important; position: relative !important; `; // Add a label to make it super clear const label = document.createElement('div'); label.style.cssText = ` position: absolute; top: -25px; left: 50%; transform: translateX(-50%); background: #2e7d32; color: white; padding: 2px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; z-index: 10000; pointer-events: none; `; label.textContent = `👆 CLICK ME: ${value}`; targetOption.style.position = 'relative'; targetOption.appendChild(label); // Scroll to the option targetOption.scrollIntoView({ behavior: 'smooth', block: 'center' }); // Clean up highlighting after 15 seconds setTimeout(() => { targetOption.style.cssText = originalStyle; if (label.parentNode) { label.parentNode.removeChild(label); } }, 15000); resolve(true); // Return true to indicate we found and highlighted the option } else { console.log(`❌ No matching option found for value: ${value}`); resolve(false); } } catch (error) { console.error('Error in selectDropdownOption:', error); resolve(false); } }); } // Function to create progress indicator function createProgressIndicator() { // Remove existing progress indicator if any const existing = document.getElementById('form-filler-progress'); if (existing) existing.remove(); // Create progress container const progressContainer = document.createElement('div'); progressContainer.id = 'form-filler-progress'; progressContainer.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10001; background: white; border-radius: 16px; padding: 24px; box-shadow: 0 8px 32px rgba(0,0,0,0.3); min-width: 400px; font-family: Arial, sans-serif; text-align: center; `; // Create title const title = document.createElement('h3'); title.style.cssText = 'margin: 0 0 16px 0; color: #333; font-size: 18px;'; title.textContent = '🚀 Filling Google Form...'; // Create progress bar container const progressBarContainer = document.createElement('div'); progressBarContainer.style.cssText = ` width: 100%; height: 8px; background: #e0e0e0; border-radius: 4px; overflow: hidden; margin: 16px 0; `; // Create progress bar const progressBar = document.createElement('div'); progressBar.id = 'form-filler-progress-bar'; progressBar.style.cssText = ` width: 0%; height: 100%; background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 4px; transition: width 0.3s ease; `; progressBarContainer.appendChild(progressBar); // Create status text const statusText = document.createElement('div'); statusText.id = 'form-filler-status'; statusText.style.cssText = 'color: #666; font-size: 14px; margin-top: 12px;'; statusText.textContent = 'Initializing...'; // Create field counter const fieldCounter = document.createElement('div'); fieldCounter.id = 'form-filler-counter'; fieldCounter.style.cssText = 'color: #999; font-size: 12px; margin-top: 8px;'; fieldCounter.textContent = '0 / 0 fields processed'; progressContainer.appendChild(title); progressContainer.appendChild(progressBarContainer); progressContainer.appendChild(statusText); progressContainer.appendChild(fieldCounter); document.body.appendChild(progressContainer); return { container: progressContainer, bar: progressBar, status: statusText, counter: fieldCounter, title: title }; } // Function to update progress function updateProgress(current, total, fieldName, success) { const progressBar = document.getElementById('form-filler-progress-bar'); const statusText = document.getElementById('form-filler-status'); const counterText = document.getElementById('form-filler-counter'); if (progressBar && statusText && counterText) { const percentage = total > 0 ? (current / total) * 100 : 0; progressBar.style.width = `${percentage}%`; if (fieldName) { const icon = success ? '✅' : '⚠️'; statusText.textContent = `${icon} ${fieldName}`; } counterText.textContent = `${current} / ${total} fields processed`; } } // Function to show success animation function showSuccessAnimation(filledCount, totalCount, skippedCount) { const progressContainer = document.getElementById('form-filler-progress'); if (!progressContainer) return; // Update to success state const title = progressContainer.querySelector('h3'); const statusText = document.getElementById('form-filler-status'); const progressBar = document.getElementById('form-filler-progress-bar'); // Success animation title.textContent = '🎉 Form Filling Complete!'; title.style.color = '#4caf50'; progressBar.style.background = 'linear-gradient(90deg, #4caf50, #66bb6a)'; progressBar.style.width = '100%'; // Create success summary const summary = document.createElement('div'); summary.style.cssText = ` margin-top: 16px; padding: 12px; background: #e8f5e8; border-radius: 8px; border-left: 4px solid #4caf50; `; // Create summary title using DOM methods (safer than innerHTML) const summaryTitle = document.createElement('div'); summaryTitle.style.cssText = 'font-weight: bold; color: #2e7d32; margin-bottom: 8px;'; summaryTitle.textContent = '📊 Summary:'; // Create summary content const summaryContent = document.createElement('div'); summaryContent.style.cssText = 'font-size: 14px; color: #333;'; // Create individual lines const filledLine = document.createElement('div'); filledLine.style.cssText = 'margin-bottom: 4px;'; filledLine.textContent = `✅ ${filledCount} fields filled successfully`; const skippedLine = document.createElement('div'); skippedLine.style.cssText = 'margin-bottom: 4px;'; skippedLine.textContent = `⚠️ ${skippedCount} fields skipped (no data)`; const totalLine = document.createElement('div'); totalLine.textContent = `📝 ${totalCount} total fields processed`; // Append all elements using DOM methods summaryContent.appendChild(filledLine); summaryContent.appendChild(skippedLine); summaryContent.appendChild(totalLine); summary.appendChild(summaryTitle); summary.appendChild(summaryContent); statusText.replaceWith(summary); // Add celebration effect progressContainer.style.animation = 'bounce 0.6s ease-in-out'; // Add CSS animation if not exists if (!document.getElementById('form-filler-animations')) { const style = document.createElement('style'); style.id = 'form-filler-animations'; style.textContent = ` @keyframes bounce { 0%, 20%, 50%, 80%, 100% { transform: translate(-50%, -50%) translateY(0); } 40% { transform: translate(-50%, -50%) translateY(-10px); } 60% { transform: translate(-50%, -50%) translateY(-5px); } } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } } .field-success { animation: pulse 0.3s ease-in-out; border: 2px solid #4caf50 !important; box-shadow: 0 0 8px rgba(76, 175, 80, 0.3) !important; } .validation-success { border-color: #4caf50 !important; box-shadow: 0 0 3px rgba(76, 175, 80, 0.2) !important; transition: all 0.3s ease; } .validation-error { border-color: #f44336 !important; box-shadow: 0 0 3px rgba(244, 67, 54, 0.2) !important; animation: shake 0.3s ease-in-out; transition: all 0.3s ease; } .validation-icon { transition: all 0.2s ease; } .validation-icon:hover { transform: translateY(-50%) scale(1.1); } .validation-tooltip { font-family: Arial, sans-serif; line-height: 1.3; } .validation-tooltip::before { content: ''; position: absolute; top: -4px; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent; border-bottom: 4px solid #f44336; } /* Improved dropdown styling */ select option { padding: 8px 12px !important; font-family: 'Courier New', monospace !important; font-size: 13px !important; line-height: 1.4 !important; white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; } select option:hover { background-color: #e3f2fd !important; } select option[value=""] { font-style: italic !important; color: #999 !important; font-family: Arial, sans-serif !important; } `; document.head.appendChild(style); } // Smart auto-close with hover detection let autoCloseTimer = null; let isHovering = false; // Function to start auto-close timer function startAutoCloseTimer() { if (autoCloseTimer) { clearTimeout(autoCloseTimer); } autoCloseTimer = setTimeout(() => { if (!isHovering && progressContainer && document.body.contains(progressContainer)) { console.log('🎉 Auto-closing success summary (no hover detected)'); closeSuccessSummary(); } }, 3000); } // Function to close the success summary function closeSuccessSummary() { if (progressContainer && document.body.contains(progressContainer)) { progressContainer.style.opacity = '0'; progressContainer.style.transform = 'translate(-50%, -50%) scale(0.9)'; progressContainer.style.transition = 'all 0.3s ease'; setTimeout(() => { if (document.body.contains(progressContainer)) { document.body.removeChild(progressContainer); } }, 300); } } // Add hover event listeners progressContainer.addEventListener('mouseenter', () => { isHovering = true; console.log('🎯 Mouse entered success summary - pausing auto-close'); // Clear the auto-close timer if (autoCloseTimer) { clearTimeout(autoCloseTimer); autoCloseTimer = null; } // Add visual feedback for hover state progressContainer.style.boxShadow = '0 12px 40px rgba(0,0,0,0.4)'; progressContainer.style.transform = 'translate(-50%, -50%) scale(1.02)'; }); progressContainer.addEventListener('mouseleave', () => { isHovering = false; console.log('🎯 Mouse left success summary - resuming auto-close in 3 seconds'); // Remove hover visual feedback progressContainer.style.boxShadow = '0 8px 32px rgba(0,0,0,0.3)'; progressContainer.style.transform = 'translate(-50%, -50%) scale(1)'; // Restart auto-close timer startAutoCloseTimer(); }); // Add manual close button for better UX const closeButton = document.createElement('button'); closeButton.style.cssText = ` position: absolute; top: 8px; right: 8px; background: #f44336; color: white; border: none; border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-size: 12px; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; `; closeButton.textContent = '×'; closeButton.title = 'Close summary'; closeButton.addEventListener('mouseenter', () => { closeButton.style.background = '#da190b'; closeButton.style.transform = 'scale(1.1)'; }); closeButton.addEventListener('mouseleave', () => { closeButton.style.background = '#f44336'; closeButton.style.transform = 'scale(1)'; }); closeButton.addEventListener('click', () => { console.log('🎯 Manual close button clicked'); closeSuccessSummary(); }); progressContainer.appendChild(closeButton); // Start the initial auto-close timer startAutoCloseTimer(); } // Function to add field success animation function animateFieldSuccess(element) { if (element) { element.classList.add('field-success'); setTimeout(() => { element.classList.remove('field-success'); }, 600); } } // Function to process all form fields with progress tracking async function fillForm() { console.log('🚀 Starting Google Form auto-fill with progress tracking...'); // Create progress indicator const progress = createProgressIndicator(); // Find all question containers const questionContainers = document.querySelectorAll('.Qr7Oae'); const totalFields = questionContainers.length; let processedFields = 0; let filledFields = 0; let skippedFields = 0; // Update initial state updateProgress(0, totalFields, 'Scanning form fields...', true); await new Promise(resolve => setTimeout(resolve, 500)); // Brief pause for UX for (let index = 0; index < questionContainers.length; index++) { const container = questionContainers[index]; try { // Find the title element const titleElement = container.querySelector('.M7eMe'); if (!titleElement) { processedFields++; updateProgress(processedFields, totalFields, 'Skipping invalid field', false); continue; } const titleText = titleElement.textContent.trim(); const value = getValueForField(titleText); processedFields++; if (!value) { console.log(`No value found for field: "${titleText}"`); skippedFields++; updateProgress(processedFields, totalFields, `Skipped: ${titleText}`, false); await new Promise(resolve => setTimeout(resolve, 200)); // Brief pause continue; } console.log(`Processing field: "${titleText}" with value: "${value}"`); updateProgress(processedFields, totalFields, `Filling: ${titleText}`, true); let success = false; // Check if it's a text input field const textInput = container.querySelector('input.whsOnd.zHQkBf'); if (textInput) { // Check if it's a date input if (textInput.type === 'date') { success = fillDateInput(textInput, value); if (success) { animateFieldSuccess(textInput); filledFields++; } console.log(`Date input filled for "${titleText}": ${success}`); } else { // Regular text input success = fillTextInput(textInput, value); if (success) { animateFieldSuccess(textInput); filledFields++; } console.log(`Text input filled for "${titleText}": ${success}`); } } // Check if it's a textarea field else { const textarea = container.querySelector('textarea.KHxj8b'); if (textarea) { success = fillTextarea(textarea, value); if (success) { animateFieldSuccess(textarea); filledFields++; } console.log(`Textarea filled for "${titleText}": ${success}`); } // Check if it's a radio button field else { const radioContainer = container.querySelector('[role="radiogroup"]'); if (radioContainer) { success = selectRadioButton(radioContainer, value); if (success) { animateFieldSuccess(radioContainer); filledFields++; } console.log(`Radio button selected for "${titleText}": ${success}`); } // Check if it's a checkbox field else { const checkboxContainer = container.querySelector('[role="list"]'); if (checkboxContainer && checkboxContainer.querySelector('[role="checkbox"]')) { success = selectCheckboxes(checkboxContainer, value); if (success) { animateFieldSuccess(checkboxContainer); filledFields++; } console.log(`Checkboxes selected for "${titleText}": ${success}`); } // Check if it's a dropdown field else { const dropdownContainer = container.querySelector('.vQES8d'); if (dropdownContainer) { success = await selectDropdownOption(dropdownContainer, value); if (success) { animateFieldSuccess(dropdownContainer); filledFields++; } console.log(`Dropdown option selected for "${titleText}": ${success}`); } else { console.log(`Unknown field type for: "${titleText}"`); skippedFields++; } } } } } // Brief pause between fields for better UX await new Promise(resolve => setTimeout(resolve, 300)); } catch (error) { console.error(`Error processing field ${index}:`, error); skippedFields++; updateProgress(processedFields, totalFields, `Error: ${titleText || 'Unknown field'}`, false); // Show detailed error for critical failures if (error.name === 'SecurityError' || error.message.includes('Permission')) { ErrorHandler.showNotification('Security restriction encountered - some fields may need manual filling', 'warning', 8000); } else { ErrorHandler.showNotification(`Field error: ${titleText || 'Unknown field'}`, 'error', 3000); } } } // Show completion animation console.log(`✅ Form filling complete! Filled: ${filledFields}, Skipped: ${skippedFields}, Total: ${totalFields}`); showSuccessAnimation(filledFields, totalFields, skippedFields); } // Function to add floating icon with menu function addFillButton() { // Check if icon already exists if (document.getElementById('form-filler-icon')) return; // Create floating icon container const iconContainer = document.createElement('div'); iconContainer.id = 'form-filler-icon'; iconContainer.style.cssText = ` position: fixed; bottom: 20px; right: 20px; z-index: 10000; cursor: pointer; transition: all 0.3s ease; `; // Create the icon image const icon = document.createElement('img'); icon.src = 'https://cdn-icons-png.flaticon.com/512/17113/17113805.png'; icon.alt = 'Form Filler'; icon.style.cssText = ` width: 50px; height: 50px; border-radius: 50%; box-shadow: 0 4px 12px rgba(0,0,0,0.3); transition: all 0.3s ease; background: #473080; padding: 8px; `; // Add hover effect to icon iconContainer.addEventListener('mouseenter', () => { icon.style.transform = 'scale(1.1)'; icon.style.boxShadow = '0 6px 16px rgba(0,0,0,0.4)'; }); iconContainer.addEventListener('mouseleave', () => { icon.style.transform = 'scale(1)'; icon.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)'; }); iconContainer.appendChild(icon); // Create menu container const menu = document.createElement('div'); menu.id = 'form-filler-menu'; menu.style.cssText = ` position: fixed; bottom: 80px; right: 20px; z-index: 9999; background: white; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.3); padding: 8px; display: none; min-width: 200px; font-family: Arial, sans-serif; `; // Create menu items const menuItems = [ { text: '⚙️ Customize Data', id: 'customize-data-option', action: async () => { hideMenu(); // Show customization modal and handle the result const shouldFillForm = await showCustomizationModal(); if (shouldFillForm) { // User clicked "Fill Form" - show instructions and fill const instructions = ` 🤖 GOOGLE FORM AUTO-FILLER READY! ✅ TEXT FIELDS: Will be filled automatically with your saved data 🎯 DROPDOWNS: Will be highlighted in green - you need to click them manually The script will now: 1. Fill all text inputs automatically with your saved values 2. Highlight dropdown options with bright green colors and labels 3. You manually click the highlighted dropdown options Ready to start filling the form? `; if (confirm(instructions)) { fillForm(); } } } }, { text: '🚀 Fill Form (Saved Data)', id: 'fill-form-option', action: () => { hideMenu(); // Fill form with current saved data const instructions = ` 🤖 GOOGLE FORM AUTO-FILLER READY! ✅ TEXT FIELDS: Will be filled automatically with your saved data 🎯 DROPDOWNS: Will be highlighted in green - you need to click them manually The script will now: 1. Fill all text inputs automatically with your saved values 2. Highlight dropdown options with bright green colors and labels 3. You manually click the highlighted dropdown options Ready to start filling the form? `; if (confirm(instructions)) { fillForm(); } } }, { text: '📋 View Mappings', id: 'view-mappings-option', action: () => { hideMenu(); showCurrentMappings(); alert('📋 Current mappings displayed in console! Press F12 to view.'); } }, { text: '🎹 Shortcuts', id: 'shortcuts-option', action: () => { hideMenu(); const shortcutsInfo = `🎹 KEYBOARD SHORTCUTS ENABLED: Alt+F: Quick Fill Form (with saved data) Alt+C: Customize Data (Toggle open/close) Alt+M: View Mappings 💡 Tips: • Alt+F instantly fills the form with your saved data • Alt+C opens/closes the customize modal (toggle) • Alt+M shows your current tag mappings in console • All shortcuts work anywhere on the Google Forms page • Shortcuts override browser defaults for better functionality`; alert(shortcutsInfo); console.log('🎹 Keyboard shortcuts enabled:'); console.log(' Alt+F: Quick Fill Form (with saved data)'); console.log(' Alt+C: Customize Data (Toggle open/close)'); console.log(' Alt+M: View Mappings'); } }, { text: '💾 Backup & Import', id: 'backup-import-option', action: () => { hideMenu(); showBackupImportModal(); } } ]; // Create menu items menuItems.forEach((item, index) => { const menuItem = document.createElement('div'); menuItem.id = item.id; menuItem.textContent = item.text; menuItem.style.cssText = ` padding: 12px 16px; cursor: pointer; border-radius: 8px; transition: background 0.2s ease; font-size: 14px; font-weight: 500; color: #333; ${index > 0 ? 'border-top: 1px solid #eee;' : ''} `; // Add hover effect menuItem.addEventListener('mouseenter', () => { menuItem.style.background = '#f5f5f5'; }); menuItem.addEventListener('mouseleave', () => { menuItem.style.background = 'transparent'; }); // Add click handler menuItem.addEventListener('click', item.action); menu.appendChild(menuItem); }); // Function to show menu function showMenu() { menu.style.display = 'block'; menu.style.opacity = '0'; menu.style.transform = 'translateY(10px)'; // Animate in setTimeout(() => { menu.style.transition = 'all 0.3s ease'; menu.style.opacity = '1'; menu.style.transform = 'translateY(0)'; }, 10); } // Function to hide menu function hideMenu() { menu.style.transition = 'all 0.3s ease'; menu.style.opacity = '0'; menu.style.transform = 'translateY(10px)'; setTimeout(() => { menu.style.display = 'none'; }, 300); } // Toggle menu on icon click let menuVisible = false; iconContainer.addEventListener('click', (e) => { e.stopPropagation(); if (menuVisible) { hideMenu(); menuVisible = false; } else { showMenu(); menuVisible = true; } }); // Hide menu when clicking outside document.addEventListener('click', (e) => { if (menuVisible && !menu.contains(e.target) && !iconContainer.contains(e.target)) { hideMenu(); menuVisible = false; } }); // Add elements to page document.body.appendChild(iconContainer); document.body.appendChild(menu); } // Keyboard shortcuts functionality function setupKeyboardShortcuts() { document.addEventListener('keydown', (e) => { // Check for Alt + F (case insensitive) if (e.altKey && (e.key === 'f' || e.key === 'F')) { // Prevent default browser behavior (like opening File menu) e.preventDefault(); e.stopPropagation(); console.log('🎹 Keyboard shortcut Alt+F triggered - Quick Fill Form'); // Quick fill form with saved data (same as "Fill Form (Saved Data)" menu option) const instructions = ` 🎹 KEYBOARD SHORTCUT ACTIVATED (Alt+F) 🤖 GOOGLE FORM AUTO-FILLER READY! ✅ TEXT FIELDS: Will be filled automatically with your saved data 🎯 DROPDOWNS: Will be highlighted in green - you need to click them manually The script will now: 1. Fill all text inputs automatically with your saved values 2. Highlight dropdown options with bright green colors and labels 3. You manually click the highlighted dropdown options Ready to start filling the form? `; if (confirm(instructions)) { fillForm(); } else { console.log('🎹 Quick fill cancelled by user'); } } // Check for Alt + C for Customize Data (Toggle) else if (e.altKey && (e.key === 'c' || e.key === 'C')) { e.preventDefault(); e.stopPropagation(); // Toggle modal - close if open, open if closed if (isCustomizationModalOpen) { console.log('🎹 Keyboard shortcut Alt+C triggered - Closing Customize Data'); window.closeCustomizationModal(); } else { console.log('🎹 Keyboard shortcut Alt+C triggered - Opening Customize Data'); // Open customization modal showCustomizationModal().then(shouldFillForm => { if (shouldFillForm) { const instructions = ` 🎹 KEYBOARD SHORTCUT RESULT (Alt+C → Fill Form) 🤖 GOOGLE FORM AUTO-FILLER READY! ✅ TEXT FIELDS: Will be filled automatically with your saved data 🎯 DROPDOWNS: Will be highlighted in green - you need to click them manually The script will now: 1. Fill all text inputs automatically with your saved values 2. Highlight dropdown options with bright green colors and labels 3. You manually click the highlighted dropdown options Ready to start filling the form? `; if (confirm(instructions)) { fillForm(); } } }); } } // Check for Alt + M for View Mappings else if (e.altKey && (e.key === 'm' || e.key === 'M')) { e.preventDefault(); e.stopPropagation(); console.log('🎹 Keyboard shortcut Alt+M triggered - View Mappings'); showCurrentMappings(); alert('📋 Current mappings displayed in console! Press F12 to view.\n\n🎹 Triggered by Alt+M shortcut'); } }); console.log('🎹 Keyboard shortcuts enabled:'); console.log(' Alt+F: Quick Fill Form (with saved data)'); console.log(' Alt+C: Customize Data'); console.log(' Alt+M: View Mappings'); } // Wait for the page to load completely function init() { // Add the fill button addFillButton(); // Setup keyboard shortcuts setupKeyboardShortcuts(); // Optional: Auto-fill on page load (uncomment if desired) // setTimeout(fillForm, 2000); console.log('🚀 Google Form Auto Filler loaded! Click the button to auto-fill text fields and highlight dropdown options.'); console.log('📋 Type showCurrentMappings() in console to view your current tag mappings.'); // Show a quick summary of available tags const tagCount = Object.keys(taggedData.tags).length; const mappingCount = Object.keys(taggedData.fieldMappings).length; console.log(`🏷️ You have ${tagCount} tags and ${mappingCount} field mappings configured.`); } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();