init commit
This commit is contained in:
392
content.js
Normal file
392
content.js
Normal file
@@ -0,0 +1,392 @@
|
||||
// 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
|
||||
});
|
||||
Reference in New Issue
Block a user