diff --git a/src/popup/popup.js b/src/popup/popup.js
index 2e488d0..6d94a53 100644
--- a/src/popup/popup.js
+++ b/src/popup/popup.js
@@ -1,9 +1,11 @@
import SubstitutionEngine from '../lib/substitution-engine.js';
+import SmartPatterns from '../lib/smart-patterns.js';
import Storage from '../lib/storage.js';
import api from '../lib/browser-polyfill.js';
// --- State ---
let mappings = [];
+let identity = {};
let settings = {};
// --- DOM refs ---
@@ -13,10 +15,12 @@ const $$ = (sel) => document.querySelectorAll(sel);
// --- Init ---
document.addEventListener('DOMContentLoaded', async () => {
mappings = await Storage.getMappings();
+ identity = await Storage.getIdentity();
settings = await Storage.getSettings();
renderMappings();
renderActivity();
+ loadIdentityForm();
updateStatusDot();
$('#enableToggle').checked = settings.enabled;
@@ -57,6 +61,9 @@ document.addEventListener('DOMContentLoaded', async () => {
$('#btnReveal').classList.toggle('active', settings.revealMode);
+ // Save identity
+ $('#btnSaveIdentity').addEventListener('click', saveIdentity);
+
// Add mapping
$('#btnAdd').addEventListener('click', addMapping);
$('#inputSub').addEventListener('keydown', (e) => {
@@ -79,6 +86,93 @@ document.addEventListener('DOMContentLoaded', async () => {
});
});
+// --- Identity ---
+function loadIdentityForm() {
+ const first = (identity.names || []).find(n => n.type === 'first');
+ const last = (identity.names || []).find(n => n.type === 'last');
+ const email = (identity.emails || [])[0];
+ const user = (identity.usernames || [])[0];
+ const phone = (identity.phones || [])[0];
+
+ if (first) {
+ $('#idFirstReal').value = first.real || '';
+ $('#idFirstSub').value = first.substitute || '';
+ }
+ if (last) {
+ $('#idLastReal').value = last.real || '';
+ $('#idLastSub').value = last.substitute || '';
+ }
+ if (email) {
+ $('#idEmailReal').value = email.real || '';
+ $('#idEmailSub').value = email.substitute || '';
+ }
+ $('#idCatchAllEmail').value = identity.catchAllEmail || '';
+ if (user) {
+ $('#idUserReal').value = user.real || '';
+ $('#idUserSub').value = user.substitute || '';
+ }
+ if (phone) {
+ $('#idPhoneReal').value = phone.real || '';
+ $('#idPhoneSub').value = phone.substitute || '';
+ }
+}
+
+async function saveIdentity() {
+ const names = [];
+ const firstReal = $('#idFirstReal').value.trim();
+ const firstSub = $('#idFirstSub').value.trim();
+ if (firstReal && firstSub) {
+ names.push({ real: firstReal, substitute: firstSub, type: 'first' });
+ }
+ const lastReal = $('#idLastReal').value.trim();
+ const lastSub = $('#idLastSub').value.trim();
+ if (lastReal && lastSub) {
+ names.push({ real: lastReal, substitute: lastSub, type: 'last' });
+ }
+
+ const emails = [];
+ const emailReal = $('#idEmailReal').value.trim();
+ const emailSub = $('#idEmailSub').value.trim();
+ if (emailReal && emailSub) {
+ emails.push({ real: emailReal, substitute: emailSub });
+ }
+
+ const usernames = [];
+ const userReal = $('#idUserReal').value.trim();
+ const userSub = $('#idUserSub').value.trim();
+ if (userReal && userSub) {
+ usernames.push({ real: userReal, substitute: userSub });
+ }
+
+ const phones = [];
+ const phoneReal = $('#idPhoneReal').value.trim();
+ const phoneSub = $('#idPhoneSub').value.trim();
+ if (phoneReal && phoneSub) {
+ phones.push({ real: phoneReal, substitute: phoneSub });
+ }
+
+ identity = {
+ names,
+ emails,
+ usernames,
+ phones,
+ catchAllEmail: $('#idCatchAllEmail').value.trim(),
+ emailDomains: identity.emailDomains || [],
+ enabled: identity.enabled || { emails: true, names: true, usernames: true, phones: true, paths: true },
+ };
+
+ await Storage.saveIdentity(identity);
+
+ // Flash save button
+ const btn = $('#btnSaveIdentity');
+ btn.textContent = 'Saved!';
+ btn.style.background = '#059669';
+ setTimeout(() => {
+ btn.textContent = 'Save Identity';
+ btn.style.background = '';
+ }, 1500);
+}
+
// --- Add Mapping ---
async function addMapping() {
const real = $('#inputReal').value.trim();
@@ -191,6 +285,7 @@ async function renderActivity() {
}
// --- Test Diff ---
+// Runs both smart patterns AND explicit mappings, shows combined result
function renderTestDiff() {
const input = $('#testInput').value;
const output = $('#diffOutput');
@@ -202,22 +297,55 @@ function renderTestDiff() {
return;
}
- const { text, replacements } = SubstitutionEngine.substitute(input, mappings);
- const chunks = SubstitutionEngine.diff(input, text, mappings);
+ // Smart patterns first (broader catches), then explicit mappings (specific overrides)
+ const smartResult = SmartPatterns.substitute(input, identity);
+ const explicitResult = SubstitutionEngine.substitute(smartResult.text, mappings);
- output.innerHTML = chunks
- .map((chunk) => {
- if (chunk.type === 'substituted') {
- return `${escapeHtml(chunk.replacement)}`;
- }
- return escapeHtml(chunk.text);
- })
- .join('');
+ const allReplacements = [...smartResult.replacements, ...explicitResult.replacements];
+ const finalText = explicitResult.text;
- stats.textContent =
- replacements.length > 0
- ? `${replacements.length} substitution${replacements.length !== 1 ? 's' : ''} would be made`
- : 'No substitutions detected';
+ // Simple diff: highlight differences
+ if (finalText === input) {
+ output.textContent = input;
+ stats.textContent = 'No substitutions detected';
+ return;
+ }
+
+ // Build a visual diff by running smart patterns on original to find positions
+ // For display, we re-run on the original to get positions
+ const smartPositions = findReplacementPositions(input, identity, mappings);
+
+ if (smartPositions.length === 0) {
+ output.textContent = finalText;
+ } else {
+ // Build highlighted output from the final text
+ // Simpler approach: show the final text with replaced values highlighted
+ let html = escapeHtml(finalText);
+ for (const r of allReplacements) {
+ const escapedReplaced = escapeHtml(r.replaced);
+ html = html.replace(
+ escapedReplaced,
+ `${escapedReplaced}`
+ );
+ }
+ output.innerHTML = html;
+ }
+
+ const smartCount = smartResult.replacements.length;
+ const explicitCount = explicitResult.replacements.length;
+ const parts = [];
+ if (smartCount > 0) parts.push(`${smartCount} smart`);
+ if (explicitCount > 0) parts.push(`${explicitCount} explicit`);
+ stats.textContent = `${allReplacements.length} substitution${allReplacements.length !== 1 ? 's' : ''} (${parts.join(', ')})`;
+}
+
+function findReplacementPositions(text, ident, maps) {
+ const positions = [];
+ const r1 = SmartPatterns.substitute(text, ident);
+ positions.push(...r1.replacements);
+ const r2 = SubstitutionEngine.substitute(r1.text, maps);
+ positions.push(...r2.replacements);
+ return positions;
}
// --- Status Dot ---