// Store current page URL for storage key const PAGE_URL = window.location.href; const STORAGE_KEY = `page_edits_${btoa(PAGE_URL)}`; const EDIT_MODE_KEY = `edit_mode_${btoa(PAGE_URL)}`; // Store original text for undo functionality const originalContent = new Map(); let editedElements = new Map(); // Track which elements we've edited let editModeEnabled = false; // Track if editing is currently enabled // Listen for messages from popup chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console.log('[Page Editor] Received message:', request); if (request.action === 'toggleEditMode') { editModeEnabled = request.enabled; console.log('[Page Editor] Edit mode toggled:', editModeEnabled); if (editModeEnabled) { // Enable editing injectOverrideCss(); makeElementsEditable(); loadSavedEdits(); attachEventListeners(); showNotification('Edit mode enabled ✓'); } else { // Disable editing disableEditingMode(); showNotification('Edit mode disabled'); } sendResponse({ success: true, editModeEnabled: editModeEnabled }); } if (request.action === 'checkEditMode') { sendResponse({ editModeEnabled: editModeEnabled }); } }); // Initialize on page load - load edits and check edit mode state function initializeEditMode() { console.log('[Page Editor] Page loaded, loading saved edits...'); // ALWAYS load saved edits (they should be visible even if not editable) loadSavedEdits(); // Check if edit mode should be enabled for this page chrome.storage.session.get([EDIT_MODE_KEY], (result) => { if (result[EDIT_MODE_KEY]) { editModeEnabled = true; console.log('[Page Editor] Edit mode was previously enabled, re-enabling...'); injectOverrideCss(); makeElementsEditable(); attachEventListeners(); } else { console.log('[Page Editor] Edit mode disabled on load'); } }); } // Show notification on page function showNotification(message) { let notification = document.getElementById('page-editor-notification'); if (!notification) { notification = document.createElement('div'); notification.id = 'page-editor-notification'; notification.className = 'page-editor-notification'; document.body.appendChild(notification); } notification.textContent = message; notification.style.opacity = '1'; notification.style.display = 'block'; setTimeout(() => { notification.style.opacity = '0'; notification.style.display = 'none'; }, 2000); } // Disable editing mode function disableEditingMode() { document.querySelectorAll('[data-editable-marked]').forEach(element => { element.removeAttribute('contenteditable'); element.removeAttribute('data-editable-marked'); element.classList.remove('editable-text'); element.style.userSelect = ''; element.style.pointerEvents = ''; element.style.cursor = ''; }); editedElements.clear(); console.log('[Page Editor] Edit mode disabled'); } // Inject CSS to override website restrictions on editing function injectOverrideCss() { // Check if already injected if (document.getElementById('page-editor-override-css')) { return; } const style = document.createElement('style'); style.id = 'page-editor-override-css'; style.innerHTML = ` [contenteditable="true"] { user-select: text !important; -webkit-user-select: text !important; -moz-user-select: text !important; pointer-events: auto !important; -webkit-user-modify: read-write-plaintext-only !important; cursor: text !important; } `; // Inject into head or html if (document.head) { document.head.appendChild(style); } else if (document.documentElement) { document.documentElement.appendChild(style); } } // Make text nodes in paragraphs, headings, divs editable function makeElementsEditable() { const editableSelectors = [ 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'span', 'div', 'article', 'section', 'blockquote', 'td', 'th', 'dd', 'dt', 'a', 'strong', 'em', 'b', 'i' ]; editableSelectors.forEach(selector => { document.querySelectorAll(selector).forEach(element => { // Skip if in head, script, style, or meta areas if (element.closest('script, style, head, noscript, meta, link, [role="presentation"]')) { return; } // Skip if already marked if (element.hasAttribute('data-editable-marked')) { return; } // Only mark if element has meaningful text content const text = element.textContent.trim(); if (text.length > 0 && text.length < 100000) { try { element.setAttribute('contenteditable', 'true'); element.setAttribute('data-editable-marked', 'true'); element.setAttribute('spellcheck', 'true'); element.classList.add('editable-text'); // Override inline styles that prevent editing element.style.userSelect = 'text'; element.style.pointerEvents = 'auto'; element.style.cursor = 'text'; // Store original content originalContent.set(element, element.innerHTML); console.log('[Page Editor] Marked element:', element.tagName, text.substring(0, 30)); } catch (e) { console.log('[Page Editor] Error marking element:', e); } } }); }); } // Attach event listeners for save on blur function attachEventListeners() { // Save on blur (user clicks away) document.addEventListener('blur', (e) => { const element = e.target; if (element.hasAttribute('data-editable-marked') && editedElements.has(element)) { console.log('[Page Editor] Blur event - saving:', element.tagName); saveEdit(element); updateAutoSaveIndicator(); editedElements.delete(element); } }, true); // Track input to know if element was changed document.addEventListener('input', (e) => { const element = e.target; if (element.hasAttribute('data-editable-marked')) { editedElements.set(element, true); console.log('[Page Editor] Input detected on:', element.tagName); } }, true); // Save on Ctrl+S document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); const activeElement = document.activeElement; if (activeElement.hasAttribute('data-editable-marked')) { console.log('[Page Editor] Ctrl+S - saving'); saveEdit(activeElement); showSaveNotification('Changes saved!'); editedElements.delete(activeElement); } } }); // Save all on Ctrl+Shift+S document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 's') { e.preventDefault(); console.log('[Page Editor] Ctrl+Shift+S - saving all'); saveAllEdits(); showSaveNotification('All changes saved!'); editedElements.clear(); } }); } // Save individual edit function saveEdit(element) { const elementId = getElementIdentifier(element); chrome.storage.local.get([STORAGE_KEY], (result) => { const edits = result[STORAGE_KEY] || {}; if (element.textContent.trim().length === 0) { delete edits[elementId]; } else { edits[elementId] = element.innerHTML; } chrome.storage.local.set({ [STORAGE_KEY]: edits }); console.log('[Page Editor] Saved edit for:', elementId); }); } // Save all edits at once function saveAllEdits() { const edits = {}; document.querySelectorAll('[data-editable-marked]').forEach(element => { const elementId = getElementIdentifier(element); if (element.textContent.trim().length > 0) { edits[elementId] = element.innerHTML; } }); chrome.storage.local.set({ [STORAGE_KEY]: edits }); console.log('[Page Editor] Saved all edits:', Object.keys(edits).length, 'elements'); } // Load and apply saved edits function loadSavedEdits() { chrome.storage.local.get([STORAGE_KEY], (result) => { if (result[STORAGE_KEY]) { const edits = result[STORAGE_KEY]; console.log('[Page Editor] Loading saved edits:', Object.keys(edits).length, 'elements'); // Try to find and restore elements by their saved paths Object.entries(edits).forEach(([elementPath, newContent]) => { try { const element = findElementByPath(elementPath); if (element) { element.innerHTML = newContent; console.log('[Page Editor] Restored:', elementPath); } } catch (e) { console.log('[Page Editor] Could not restore:', elementPath, e.message); } }); } else { console.log('[Page Editor] No saved edits for this page'); } }); } // Find element by its XPath-like identifier function findElementByPath(path) { let parts = path.split('/'); let element = document.body; for (let part of parts) { const match = part.match(/(\w+)\[(\d+)\]/); if (!match) continue; const tagName = match[1].toUpperCase(); const index = parseInt(match[2]); let count = 0; let found = false; for (let child of element.children) { if (child.tagName === tagName) { if (count === index) { element = child; found = true; break; } count++; } } if (!found) return null; } return element; } // Generate unique identifier for element (xPath or similar) function getElementIdentifier(element) { let path = []; let current = element; while (current && current !== document.body) { let index = 0; let sibling = current.previousElementSibling; while (sibling) { if (sibling.tagName === current.tagName) { index++; } sibling = sibling.previousElementSibling; } path.unshift(`${current.tagName.toLowerCase()}[${index}]`); current = current.parentElement; } return path.join('/'); } // Show auto-save indicator function updateAutoSaveIndicator() { let indicator = document.getElementById('page-editor-indicator'); if (!indicator) { indicator = document.createElement('div'); indicator.id = 'page-editor-indicator'; indicator.className = 'page-editor-indicator'; document.body.appendChild(indicator); } indicator.textContent = 'Auto-saving...'; indicator.style.opacity = '1'; setTimeout(() => { indicator.style.opacity = '0'; }, 2000); } // Show save notification function showSaveNotification(message) { let notification = document.getElementById('page-editor-notification'); if (!notification) { notification = document.createElement('div'); notification.id = 'page-editor-notification'; notification.className = 'page-editor-notification'; document.body.appendChild(notification); } notification.textContent = message; notification.style.opacity = '1'; notification.style.display = 'block'; setTimeout(() => { notification.style.opacity = '0'; notification.style.display = 'none'; }, 3000); } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeEditMode); } else { initializeEditMode(); } // Re-initialize on dynamic content (MutationObserver) const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList') { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { makeElementsEditable(); } }); } }); }); observer.observe(document.body, { childList: true, subtree: true });