Files
page-editor/content.js
2026-06-06 08:28:22 +05:30

393 lines
12 KiB
JavaScript

// 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
});