diff --git a/filler.js b/filler.js new file mode 100644 index 0000000..0931a20 --- /dev/null +++ b/filler.js @@ -0,0 +1,101 @@ +// filler.js — Script executed on the active tab to auto-fill form fields. + +(function() { + // Retrieve the profile data passed from the popup/scripting context + const profile = window.__autofillProfileData; + if (!profile) { + console.error("Autofill profile data not found."); + return "Error: Profile data not found"; + } + + console.log("Starting auto-fill with profile:", profile); + + // Helper to find associated label text for an input + function getLabelText(input) { + let labelText = ""; + + // 1. Check aria-label or placeholder + if (input.getAttribute("aria-label")) { + labelText += " " + input.getAttribute("aria-label"); + } + if (input.getAttribute("placeholder")) { + labelText += " " + input.getAttribute("placeholder"); + } + if (input.getAttribute("id")) { + const label = document.querySelector(`label[for="${input.getAttribute("id")}"]`); + if (label) { + labelText += " " + label.innerText; + } + } + + // 2. Check parent label + let parent = input.parentElement; + while (parent && parent !== document.body) { + if (parent.tagName === "LABEL") { + labelText += " " + parent.innerText; + break; + } + parent = parent.parentElement; + } + + // 3. Check name or id attributes + if (input.name) labelText += " " + input.name; + if (input.id) labelText += " " + input.id; + + return labelText.toLowerCase(); + } + + // Find all input, textarea, and select elements + const inputs = document.querySelectorAll("input, textarea, select"); + let filledCount = 0; + + inputs.forEach(input => { + // Skip hidden or read-only elements + if (input.type === "hidden" || input.disabled || input.readOnly) return; + + const label = getLabelText(input); + + let valToSet = null; + + // Matching logic based on common field names + if (label.includes("email") || label.includes("mail")) { + valToSet = profile.email; + } else if (label.includes("phone") || label.includes("tel") || label.includes("mobile") || label.includes("contact")) { + valToSet = profile.phone; + } else if (label.includes("linkedin")) { + valToSet = profile.linkedin; + } else if (label.includes("github")) { + valToSet = profile.github; + } else if (label.includes("first name") || label.includes("firstname")) { + valToSet = profile.firstName; + } else if (label.includes("last name") || label.includes("lastname")) { + valToSet = profile.lastName; + } else if (label.includes("full name") || label.includes("fullname") || (label.includes("name") && !label.includes("company") && !label.includes("school") && !label.includes("employer"))) { + valToSet = profile.fullName; + } else if (label.includes("website") || label.includes("portfolio") || label.includes("personal link")) { + valToSet = profile.portfolio || profile.linkedin; + } else if (label.includes("cover letter") || label.includes("describe") || label.includes("introduce yourself")) { + valToSet = profile.summary || ""; + } + + if (valToSet !== null && valToSet !== undefined) { + // Set the value + input.value = valToSet; + + // Highlight the filled field briefly + const originalBg = input.style.backgroundColor; + input.style.backgroundColor = "rgba(0, 214, 126, 0.2)"; + input.style.transition = "background-color 0.5s ease"; + setTimeout(() => { + input.style.backgroundColor = originalBg; + }, 1500); + + // Trigger change/input events so any framework (React/Angular/Vue) registers the value + input.dispatchEvent(new Event("input", { bubbles: true })); + input.dispatchEvent(new Event("change", { bubbles: true })); + filledCount++; + } + }); + + return `Filled ${filledCount} fields successfully!`; +})(); diff --git a/popup.html b/popup.html index ba4a238..af28632 100644 --- a/popup.html +++ b/popup.html @@ -414,6 +414,20 @@
+ +
+
+ ⚡ Quick Actions +
+ +

+ Fills fields on the current page using your profile details. +

+
+ +
diff --git a/popup.js b/popup.js index cfd692f..53d3561 100644 --- a/popup.js +++ b/popup.js @@ -169,6 +169,99 @@ document.getElementById('clear-all-btn').addEventListener('click', () => { } }); +// ─── Autofill Functionality ────────────────────────────────────────────────── + +function parseProfileText(text) { + const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/; + const phoneRegex = /\+?[0-9]{1,4}[ \t.-]?[0-9]{3,4}[ \t.-]?[0-9]{3,4}/; + const linkedinRegex = /(https?:\/\/)?(www\.)?linkedin\.com\/in\/[a-zA-Z0-9_-]+/; + const githubRegex = /(https?:\/\/)?(www\.)?github\.com\/[a-zA-Z0-9_-]+/; + + const emailMatch = text.match(emailRegex); + const phoneMatch = text.match(phoneRegex); + const linkedinMatch = text.match(linkedinRegex); + const githubMatch = text.match(githubRegex); + + // Extract Name (first line usually) + const lines = text.split('\n').map(l => l.trim()).filter(Boolean); + let fullName = "Hamza Ayed"; + if (lines.length > 0) { + const firstLine = lines[0].replace(/—.*/, '').replace(/:.*/, '').trim(); + fullName = firstLine; + } + + const nameParts = fullName.split(' '); + const firstName = nameParts[0] || ""; + const lastName = nameParts.slice(1).join(' ') || ""; + + // Extract Summary + let summary = ""; + const summaryIdx = text.toLowerCase().indexOf("summary:"); + if (summaryIdx !== -1) { + const skillsIdx = text.toLowerCase().indexOf("core skills:", summaryIdx); + if (skillsIdx !== -1) { + summary = text.substring(summaryIdx + 8, skillsIdx).trim(); + } else { + summary = text.substring(summaryIdx + 8, summaryIdx + 500).trim(); + } + } + + return { + fullName, + firstName, + lastName, + email: emailMatch ? emailMatch[0] : "", + phone: phoneMatch ? phoneMatch[0] : "", + linkedin: linkedinMatch ? linkedinMatch[0] : "", + github: githubMatch ? githubMatch[0] : "", + summary, + cvText: text + }; +} + +document.getElementById('autofill-btn').addEventListener('click', async () => { + const userProfile = document.getElementById('profile-textarea').value.trim(); + if (!userProfile) { + showToast('⚠️ Please enter profile info first', 'error'); + return; + } + + const profileData = parseProfileText(userProfile); + + try { + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + if (!tab) { + showToast('❌ No active tab found', 'error'); + return; + } + + // Set the profile data on the tab window context + await chrome.scripting.executeScript({ + target: { tabId: tab.id }, + func: (data) => { + window.__autofillProfileData = data; + }, + args: [profileData] + }); + + // Execute the filler script + const results = await chrome.scripting.executeScript({ + target: { tabId: tab.id }, + files: ['filler.js'] + }); + + if (results && results[0]) { + showToast('✨ ' + results[0].result, 'success'); + } else { + showToast('✨ Autofill completed!', 'success'); + } + } catch (e) { + console.error(e); + showToast('❌ Error: ' + e.message, 'error'); + } +}); + // ─── Init ──────────────────────────────────────────────────────────────────── loadSettings(); +