init commit
This commit is contained in:
121
CHANGES_SUMMARY.txt
Normal file
121
CHANGES_SUMMARY.txt
Normal file
@@ -0,0 +1,121 @@
|
||||
╔════════════════════════════════════════════════════════════════════════════╗
|
||||
║ PAGE EDITOR - UPDATE SUMMARY ║
|
||||
║ Yellow Highlights Removed + Toggle Mode Added ║
|
||||
╚════════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
🔧 FILES MODIFIED:
|
||||
|
||||
1. content.js
|
||||
✅ Added message listener for popup toggle
|
||||
✅ Changed initialization to check toggle state
|
||||
✅ Only enables editing when toggle is ON
|
||||
✅ Can disable editing (removeAttribute contenteditable)
|
||||
✅ Added showNotification() function
|
||||
|
||||
2. popup.html
|
||||
✅ Complete redesign with toggle switch
|
||||
✅ Removed "all elements are editable" info
|
||||
✅ Added toggle container with visual switch
|
||||
✅ Status text shows Enabled/Disabled
|
||||
✅ Edit info only shows when enabled
|
||||
✅ Action buttons only show when enabled
|
||||
✅ Keyboard shortcuts only show when enabled
|
||||
|
||||
3. popup.js
|
||||
✅ Complete rewrite with toggle logic
|
||||
✅ Manages edit mode state in chrome.storage.session
|
||||
✅ Sends toggleEditMode message to content script
|
||||
✅ Updates UI based on toggle state
|
||||
✅ Disables/enables buttons appropriately
|
||||
✅ Shows/hides sections based on mode
|
||||
|
||||
4. styles.css
|
||||
✅ Removed all yellow highlight styles
|
||||
✅ Removed outline, background-color from contenteditable
|
||||
✅ Only keeps cursor: text
|
||||
✅ Minimal, non-intrusive styling
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
⚙️ HOW IT WORKS NOW:
|
||||
|
||||
Page Load:
|
||||
├─ Content script initializes (but does NOT enable editing)
|
||||
└─ Checks if edit mode toggle was previously enabled
|
||||
|
||||
Popup Click (Toggle ON):
|
||||
├─ Popup sends "toggleEditMode: true" to content script
|
||||
├─ Content script injects CSS overrides
|
||||
├─ Makes all text elements contenteditable
|
||||
├─ Loads saved edits
|
||||
├─ Attaches event listeners
|
||||
└─ Shows notification: "Edit mode enabled ✓"
|
||||
|
||||
Popup Click (Toggle OFF):
|
||||
├─ Popup sends "toggleEditMode: false" to content script
|
||||
├─ Content script removes contenteditable from all elements
|
||||
├─ Clears edit tracking
|
||||
├─ Shows notification: "Edit mode disabled"
|
||||
└─ Page returns to normal appearance
|
||||
|
||||
User Edits Text:
|
||||
├─ Clicks on editable text
|
||||
├─ Text becomes focused (browser's default selection)
|
||||
├─ Types new content
|
||||
├─ Presses Ctrl+S OR clicks away
|
||||
└─ Changes auto-save to chrome.storage.local
|
||||
|
||||
Page Reload:
|
||||
├─ Content script checks edit mode state
|
||||
├─ If enabled: restores editable elements and loads saved edits
|
||||
├─ If disabled: page loads normally
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
🎯 USER EXPERIENCE:
|
||||
|
||||
BEFORE:
|
||||
1. Yellow highlights everywhere (distracting)
|
||||
2. Can't turn off editing
|
||||
3. Edit mode always active
|
||||
4. Page looks modified
|
||||
|
||||
AFTER:
|
||||
1. Page looks completely normal
|
||||
2. Click toggle to enable editing
|
||||
3. Edit only when needed
|
||||
4. No visual pollution
|
||||
5. Clean, minimal interface
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
🚀 RELOAD INSTRUCTIONS:
|
||||
|
||||
1. chrome://extensions/
|
||||
2. Find "Page Editor"
|
||||
3. Click reload icon (circular arrow)
|
||||
4. That's it!
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
✨ FEATURES MAINTAINED:
|
||||
|
||||
✓ Edit any text element
|
||||
✓ Auto-save on blur (click away)
|
||||
✓ Ctrl+S to save
|
||||
✓ Ctrl+Shift+S to save all
|
||||
✓ Persistent storage across reloads
|
||||
✓ Export edits as JSON
|
||||
✓ Clear all edits
|
||||
✓ Works on any website
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
📝 NO MORE:
|
||||
|
||||
✗ Yellow highlights on page
|
||||
✗ Confusing visual indicators
|
||||
✗ Always-on editing mode
|
||||
✗ Can't disable editing per-page
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
190
EXTENSION_OVERVIEW.txt
Normal file
190
EXTENSION_OVERVIEW.txt
Normal file
@@ -0,0 +1,190 @@
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ PAGE EDITOR CHROME EXTENSION ║
|
||||
║ Complete Structure ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
📁 Project Structure:
|
||||
├── manifest.json [Configuration & permissions]
|
||||
├── content.js [Main script that runs on pages]
|
||||
├── background.js [Service worker for extension]
|
||||
├── popup.html [Extension popup UI]
|
||||
├── popup.js [Popup interactions]
|
||||
├── styles.css [Styling for editable elements]
|
||||
├── README.md [Full documentation]
|
||||
├── QUICKSTART.md [Installation & quick start guide]
|
||||
└── EXTENSION_OVERVIEW.txt [This file]
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
🎯 KEY FEATURES:
|
||||
|
||||
✏️ Edit Any Text
|
||||
- Click any text element on any website to edit it
|
||||
- Visual feedback with yellow highlight on hover
|
||||
- Works on paragraphs, headings, lists, divs, and more
|
||||
|
||||
💾 Auto-Save
|
||||
- Changes auto-save when you click outside an element
|
||||
- Manual save with Ctrl+S
|
||||
- Save all with Ctrl+Shift+S
|
||||
|
||||
🔄 Persistent Storage
|
||||
- Uses Chrome's Local Storage API
|
||||
- Changes survive page reloads
|
||||
- Per-domain storage (each website has separate edits)
|
||||
|
||||
📥 Export & Backup
|
||||
- Export all edits as JSON file
|
||||
- Backup your changes before clearing browser data
|
||||
- Import workflow (manual JSON editing)
|
||||
|
||||
🧹 Clear & Reset
|
||||
- Clear all edits for a page
|
||||
- Reset to original website content
|
||||
- Individual element clearing by deleting text
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
🔧 HOW IT WORKS:
|
||||
|
||||
1. CONTENT INJECTION (content.js)
|
||||
├─ Runs on every webpage (all_urls match)
|
||||
├─ Makes text elements contenteditable
|
||||
├─ Adds visual styling with styles.css
|
||||
├─ Detects blur events and auto-saves
|
||||
├─ Loads saved edits on page load
|
||||
└─ Monitors for new dynamic content
|
||||
|
||||
2. STORAGE (chrome.storage.local)
|
||||
├─ Key: "page_edits_<base64_encoded_url>"
|
||||
├─ Value: { element_id: new_html, ... }
|
||||
├─ Persists across sessions
|
||||
└─ ~10MB limit per domain
|
||||
|
||||
3. POPUP INTERFACE (popup.html/js)
|
||||
├─ Shows edit count
|
||||
├─ Save all changes button
|
||||
├─ Clear all edits button
|
||||
├─ Export edits as JSON
|
||||
└─ Keyboard shortcuts guide
|
||||
|
||||
4. BACKGROUND SERVICE WORKER (background.js)
|
||||
└─ Minimal - mostly handles popup interactions
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
⌨️ KEYBOARD SHORTCUTS:
|
||||
|
||||
Ctrl+S (Cmd+S on Mac) Save current edit
|
||||
Ctrl+Shift+S (Cmd+Shift+S) Save all edits on page
|
||||
Click Edit any text element
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
📋 INSTALLATION CHECKLIST:
|
||||
|
||||
☐ 1. Open chrome://extensions/
|
||||
☐ 2. Enable "Developer mode" (top right toggle)
|
||||
☐ 3. Click "Load unpacked"
|
||||
☐ 4. Select the page_editor folder
|
||||
☐ 5. Extension should appear in your list
|
||||
☐ 6. Extension icon appears in toolbar
|
||||
☐ 7. Visit any website to test
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
🧪 TEST IT:
|
||||
|
||||
1. Open https://example.com
|
||||
2. You'll see yellow highlights on text elements
|
||||
3. Click "Example Domain" heading to edit it
|
||||
4. Change it to anything, click outside
|
||||
5. Reload the page (Ctrl+R)
|
||||
6. Your change persists! ✨
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
📝 MANIFEST DETAILS:
|
||||
|
||||
manifest_version: 3 (latest Chrome standard)
|
||||
Permissions:
|
||||
- storage: Save/load edits from local storage
|
||||
- scripting: Inject content script
|
||||
|
||||
Host Permissions:
|
||||
- <all_urls>: Works on any website
|
||||
|
||||
Content Scripts:
|
||||
- Runs on all URLs
|
||||
- Loads content.js and styles.css at document start
|
||||
- Executes before page content loads
|
||||
|
||||
Action (Popup):
|
||||
- popup.html: Shows controls and status
|
||||
- Default title: "Page Editor"
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
💾 STORAGE MECHANISM:
|
||||
|
||||
URL: https://example.com/page
|
||||
Storage Key: page_edits_aHR0cHM6Ly9leGFtcGxlLmNvbS9wYWdl
|
||||
|
||||
Storage Value Example:
|
||||
{
|
||||
"DIV[0]/P[1]": "<strong>New content</strong>",
|
||||
"DIV[0]/H1[0]": "Updated heading"
|
||||
}
|
||||
|
||||
Element identification uses XPath-like structure for reliability.
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
🎨 STYLING:
|
||||
|
||||
Editable Elements:
|
||||
├─ Default: 1px gray outline + light yellow background
|
||||
├─ Hover: Yellow outline + darker yellow background
|
||||
└─ Focus: 2px blue outline + light blue background
|
||||
|
||||
Notifications:
|
||||
├─ Auto-save indicator (bottom right)
|
||||
└─ Save confirmation (top right)
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
⚠️ IMPORTANT NOTES:
|
||||
|
||||
1. Changes are stored LOCALLY in your browser
|
||||
- Not synced across devices
|
||||
- Not uploaded anywhere
|
||||
- Only visible to you on this browser
|
||||
|
||||
2. Some websites may block editing due to Content Security Policy
|
||||
|
||||
3. Clearing browser cache will also clear your edits
|
||||
- Use "Export Edits" to backup before clearing
|
||||
|
||||
4. Maximum ~10MB storage per domain
|
||||
|
||||
5. Dynamic content added after page load needs page refresh
|
||||
to become editable
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
📞 SUPPORT:
|
||||
|
||||
For issues, check:
|
||||
1. README.md - Full feature documentation
|
||||
2. QUICKSTART.md - Installation and quick reference
|
||||
3. Browser console (F12) for error messages
|
||||
4. chrome://extensions/ - Extension status and logs
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
🚀 READY TO USE!
|
||||
|
||||
The extension is fully functional and ready to install.
|
||||
Follow the installation steps in QUICKSTART.md to get started.
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
128
QUICKSTART.md
Normal file
128
QUICKSTART.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Quick Start Guide
|
||||
|
||||
## Installation Steps
|
||||
|
||||
1. **Open Chrome Extensions Page**
|
||||
```
|
||||
chrome://extensions/
|
||||
```
|
||||
|
||||
2. **Enable Developer Mode**
|
||||
- Click the toggle switch in the top right corner
|
||||
|
||||
3. **Load the Extension**
|
||||
- Click "Load unpacked"
|
||||
- Navigate to this folder and select it
|
||||
- The extension should appear in your list
|
||||
|
||||
4. **Verify Installation**
|
||||
- You should see the "Page Editor" extension in your list with status "Enabled"
|
||||
- An icon should appear in your Chrome toolbar (top right)
|
||||
|
||||
## First Test
|
||||
|
||||
1. Go to any website (e.g., https://example.com)
|
||||
2. You'll notice text elements have a yellow highlight (editable)
|
||||
3. **Click any text** to edit it
|
||||
4. **Click outside** the text or press Enter to save
|
||||
5. **Reload the page** (Ctrl+R or Cmd+R)
|
||||
6. Your changes should still be there!
|
||||
|
||||
## Using the Popup
|
||||
|
||||
1. Click the **Page Editor icon** in your Chrome toolbar
|
||||
2. The popup shows:
|
||||
- Number of elements you've edited on the current page
|
||||
- **Save All Changes** - Force save all current edits
|
||||
- **Clear All Edits** - Remove all edits for this page
|
||||
- **Export Edits** - Download edits as JSON
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```
|
||||
1. Visit https://example.com
|
||||
2. Click the heading "Example Domain"
|
||||
3. Change it to "My Custom Title"
|
||||
4. Click outside to auto-save
|
||||
5. Reload the page
|
||||
6. Title remains "My Custom Title"
|
||||
7. Open popup and click "Export Edits"
|
||||
8. A JSON file is downloaded with your edits
|
||||
```
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Ctrl+S` | Save current edit |
|
||||
| `Ctrl+Shift+S` | Save all edits |
|
||||
| Click | Edit text |
|
||||
|
||||
## Clearing Edits
|
||||
|
||||
**To clear all edits for a page:**
|
||||
1. Click the extension icon
|
||||
2. Click "Clear All Edits"
|
||||
3. Confirm the action
|
||||
4. The page will reload with original content
|
||||
|
||||
**To clear individual edits:**
|
||||
1. Click the text to edit it
|
||||
2. Delete all content (make it empty)
|
||||
3. Click outside to save
|
||||
4. The element will return to original on reload
|
||||
|
||||
## Storage Info
|
||||
|
||||
- Edits are stored in Chrome's **Local Storage**
|
||||
- Storage is **per-domain** (separate edits for each website)
|
||||
- Maximum ~10MB of storage per browser
|
||||
- Data persists until you:
|
||||
- Click "Clear All Edits" in the popup
|
||||
- Clear browser cache/cookies (includes local storage)
|
||||
- Uninstall the extension
|
||||
|
||||
## Tips & Tricks
|
||||
|
||||
✅ **Works on:**
|
||||
- News sites
|
||||
- Documentation pages
|
||||
- Forums and discussion boards
|
||||
- Any text-based content
|
||||
|
||||
❌ **May not work on:**
|
||||
- Sites with strict Content Security Policy (CSP)
|
||||
- Password-protected pages
|
||||
- Websites that heavily restrict JavaScript
|
||||
|
||||
💡 **Best practices:**
|
||||
- Export important edits regularly
|
||||
- Don't clear browser data if you want to keep edits
|
||||
- Reload page if new text elements don't become editable
|
||||
- Use Clear All Edits to reset a page to original
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Q: Extension won't load**
|
||||
- A: Make sure Developer Mode is enabled in chrome://extensions/
|
||||
- Check the file path and ensure all files are present
|
||||
|
||||
**Q: Can't edit text on some websites**
|
||||
- A: Some sites have strict CSP policies that prevent editing
|
||||
- This is a security feature of those websites
|
||||
|
||||
**Q: Edits disappeared**
|
||||
- A: Check if you cleared browsing data (includes local storage)
|
||||
- Export edits before clearing cache
|
||||
|
||||
**Q: Getting "Manifest error"**
|
||||
- A: Ensure manifest.json is in the main directory
|
||||
- Check that all referenced files exist (content.js, styles.css, etc.)
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Install the extension and test it
|
||||
- Visit any website and try editing some text
|
||||
- Reload the page to see changes persist
|
||||
- Use the popup to view and manage your edits
|
||||
- Export edits as backup when needed
|
||||
96
README.md
Normal file
96
README.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Page Editor Chrome Extension
|
||||
|
||||
A Chrome extension that lets you edit and save text content on any website locally. All changes persist across page reloads.
|
||||
|
||||
## Features
|
||||
|
||||
✨ **Edit Any Text** - Click on any text element to edit it directly
|
||||
💾 **Auto-Save** - Changes are automatically saved to your browser's local storage
|
||||
🔄 **Persistent** - Edits persist across page reloads
|
||||
📥 **Export** - Export all your edits as a JSON file
|
||||
🎨 **Visual Feedback** - Yellow highlight shows editable elements
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone or download this repository
|
||||
2. Open Chrome and navigate to `chrome://extensions/`
|
||||
3. Enable "Developer mode" (toggle in the top right)
|
||||
4. Click "Load unpacked" and select the `page_editor` directory
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Editing
|
||||
- **Click any text** on the page to start editing (text will be highlighted in yellow)
|
||||
- **Type to edit** the content
|
||||
- **Click outside** or move to another element to auto-save
|
||||
|
||||
### Keyboard Shortcuts
|
||||
- `Ctrl+S` (or `Cmd+S` on Mac) - Save current edit
|
||||
- `Ctrl+Shift+S` (or `Cmd+Shift+S` on Mac) - Save all edits on page
|
||||
- `Click` - Edit any text element
|
||||
|
||||
### Extension Popup
|
||||
- **Save All Changes** - Manually save all edits
|
||||
- **Clear All Edits** - Delete all edits for the current page
|
||||
- **Export Edits** - Download all edits as a JSON file
|
||||
- **View edit count** - See how many elements you've edited
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Content Script** (`content.js`) injects into every webpage and:
|
||||
- Makes all text elements editable (contenteditable)
|
||||
- Adds visual styling to show editable elements
|
||||
- Saves edits to Chrome's local storage using page URL as key
|
||||
|
||||
2. **Storage** - Uses `chrome.storage.local` to persist edits with a key based on the page URL (Base64 encoded)
|
||||
|
||||
3. **Automatic Reload** - On page reload, the extension loads saved edits and applies them automatically
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Storage Key Format**: `page_edits_<base64_encoded_url>`
|
||||
- **Element Identification**: Uses XPath-like structure to identify elements reliably
|
||||
- **Editable Elements**: `<p>`, `<h1>-<h6>`, `<li>`, `<span>`, `<div>`, `<article>`, `<section>`
|
||||
- **Excluded Elements**: Script tags, style tags, head elements are automatically excluded
|
||||
|
||||
## Limitations
|
||||
|
||||
- Changes are stored locally in your browser's storage (not synced across devices)
|
||||
- Some websites with strict Content Security Policy (CSP) may not support editing
|
||||
- Dynamic content added after page load needs a page refresh to become editable
|
||||
- Maximum storage per domain: ~10MB (shared with all sites)
|
||||
|
||||
## Tips
|
||||
|
||||
- Use `Clear All Edits` to reset a page to its original state
|
||||
- Export your edits regularly for backup
|
||||
- Changes don't affect the original website—they're only visible to you locally
|
||||
- Clearing browser data will also clear your edits
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Edits not saving?**
|
||||
- Check if the website has restrictive CSP headers
|
||||
- Reload the page and try again
|
||||
- Check chrome://extensions/ to ensure the extension is enabled
|
||||
|
||||
**Can't edit certain elements?**
|
||||
- Some websites apply styles that prevent editing
|
||||
- Try editing parent or child elements instead
|
||||
|
||||
**Lost edits after clearing cache?**
|
||||
- Chrome's "Clear browsing data" will delete extension storage
|
||||
- Use the Export feature to backup important edits
|
||||
|
||||
## Development
|
||||
|
||||
The extension consists of:
|
||||
- `manifest.json` - Extension configuration
|
||||
- `content.js` - Main script that makes elements editable
|
||||
- `background.js` - Service worker for background tasks
|
||||
- `popup.html/js` - Extension popup UI
|
||||
- `styles.css` - Styling for editable elements
|
||||
|
||||
## License
|
||||
|
||||
MIT - Free to use and modify
|
||||
58
UPDATE_GUIDE.md
Normal file
58
UPDATE_GUIDE.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Update Complete! 🎉
|
||||
|
||||
## What Changed
|
||||
|
||||
✅ **Removed yellow highlights** - Text appears completely normal
|
||||
✅ **Added Edit Mode toggle** - Edit only when toggled ON from popup
|
||||
✅ **Cleaner UI** - Simple on/off switch in the extension popup
|
||||
|
||||
## How to Update
|
||||
|
||||
1. Go to `chrome://extensions/`
|
||||
2. Find "Page Editor" in your list
|
||||
3. Click the **reload icon** (circular arrow) on the extension card
|
||||
4. Done! The extension is updated
|
||||
|
||||
## New Workflow
|
||||
|
||||
### Enable Edit Mode
|
||||
1. Click the **Page Editor** extension icon
|
||||
2. Toggle the **Edit Mode** switch to ON
|
||||
3. The page is now editable
|
||||
4. Click any text to edit it
|
||||
5. Changes auto-save when you click away
|
||||
|
||||
### Disable Edit Mode
|
||||
1. Click the **Page Editor** extension icon
|
||||
2. Toggle the **Edit Mode** switch to OFF
|
||||
3. Page returns to normal (no highlights, no editing)
|
||||
|
||||
## Key Improvements
|
||||
|
||||
| Before | After |
|
||||
|--------|-------|
|
||||
| Yellow highlights everywhere | No visual indicators |
|
||||
| Always editable | Only when toggled ON |
|
||||
| Hard to disable | Simple toggle switch |
|
||||
| Distracting | Clean and minimal |
|
||||
|
||||
## Features Still Available
|
||||
|
||||
✓ Auto-save when clicking away
|
||||
✓ Ctrl+S to save current edit
|
||||
✓ Ctrl+Shift+S to save all edits
|
||||
✓ Clear all edits button
|
||||
✓ Export edits as JSON
|
||||
✓ Edits persist across page reloads
|
||||
|
||||
## Test It Now
|
||||
|
||||
1. **Reload the extension** (chrome://extensions/)
|
||||
2. **Visit any website** (example.com)
|
||||
3. **Click the extension icon**
|
||||
4. **Toggle "Edit Mode" ON**
|
||||
5. **Click text to edit** - no highlights, just works!
|
||||
6. **Reload page** - edits persist
|
||||
7. **Toggle "Edit Mode" OFF** - back to normal website
|
||||
|
||||
Enjoy your clean, on-demand editing! 🚀
|
||||
19
background.js
Normal file
19
background.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// Background service worker for the Page Editor extension
|
||||
|
||||
// Listen for extension icon click
|
||||
chrome.action.onClicked.addListener((tab) => {
|
||||
// Open popup or perform action
|
||||
chrome.scripting.executeScript({
|
||||
target: { tabId: tab.id },
|
||||
function: () => {
|
||||
console.log('Page Editor is active');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Clean up old storage entries
|
||||
chrome.storage.local.get(null, (items) => {
|
||||
// Log storage usage
|
||||
const storageSize = JSON.stringify(items).length;
|
||||
console.log(`Page Editor storage usage: ${(storageSize / 1024).toFixed(2)} KB`);
|
||||
});
|
||||
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
|
||||
});
|
||||
34
manifest.json
Normal file
34
manifest.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Page Editor",
|
||||
"version": "1.0.0",
|
||||
"description": "Edit and save text on any webpage locally",
|
||||
"permissions": [
|
||||
"storage",
|
||||
"scripting"
|
||||
],
|
||||
"host_permissions": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"js": [
|
||||
"content.js"
|
||||
],
|
||||
"css": [
|
||||
"styles.css"
|
||||
],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_title": "Page Editor"
|
||||
}
|
||||
}
|
||||
233
popup.html
Normal file
233
popup.html
Normal file
@@ -0,0 +1,233 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Page Editor</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 350px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.toggle-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: white;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.toggle-label {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Toggle switch */
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
height: 28px;
|
||||
background-color: #ccc;
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.toggle-switch.enabled {
|
||||
background-color: #4285f4;
|
||||
}
|
||||
|
||||
.toggle-switch::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
|
||||
.toggle-switch.enabled::after {
|
||||
left: 24px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-text.active {
|
||||
color: #2e7d32;
|
||||
background-color: #e8f5e9;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.edit-info {
|
||||
background-color: #e3f2fd;
|
||||
border-left: 4px solid #4285f4;
|
||||
padding: 12px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
color: #1565c0;
|
||||
line-height: 1.5;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.edit-info.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid #ddd;
|
||||
background-color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #f9f9f9;
|
||||
border-color: #4285f4;
|
||||
color: #4285f4;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
background-color: #4285f4;
|
||||
color: white;
|
||||
border-color: #4285f4;
|
||||
}
|
||||
|
||||
button.primary:hover {
|
||||
background-color: #3367d6;
|
||||
border-color: #3367d6;
|
||||
}
|
||||
|
||||
button.danger {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
border-color: #f44336;
|
||||
}
|
||||
|
||||
button.danger:hover {
|
||||
background-color: #da190b;
|
||||
border-color: #da190b;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#editCount {
|
||||
font-weight: 600;
|
||||
color: #4285f4;
|
||||
}
|
||||
|
||||
.keyboard-shortcuts {
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.keyboard-shortcuts.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.keyboard-shortcuts code {
|
||||
background-color: #fff;
|
||||
padding: 2px 6px;
|
||||
border-radius: 2px;
|
||||
font-family: 'Monaco', 'Courier New', monospace;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>✏️ Page Editor</h1>
|
||||
|
||||
<div class="toggle-container">
|
||||
<div class="toggle-label">Edit Mode</div>
|
||||
<div class="toggle-switch" id="editModeToggle"></div>
|
||||
</div>
|
||||
<div class="status-text" id="statusText">Disabled</div>
|
||||
|
||||
<div class="edit-info" id="editInfo">
|
||||
When enabled, you can click any text to edit it. Changes save automatically when you click away.
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Status</div>
|
||||
<div id="editStatus">No edits yet</div>
|
||||
</div>
|
||||
|
||||
<div class="section" id="editActionsSection" style="display: none;">
|
||||
<div class="section-title">Actions</div>
|
||||
<button class="primary" id="saveAllBtn">💾 Save All Changes</button>
|
||||
<button id="clearAllBtn">🔄 Clear All Edits</button>
|
||||
<button id="exportBtn">📥 Export Edits</button>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Keyboard Shortcuts</div>
|
||||
<div class="keyboard-shortcuts" id="keyboardShortcuts">
|
||||
<div><code>Ctrl+S</code> Save current edit</div>
|
||||
<div><code>Ctrl+Shift+S</code> Save all edits</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
318
popup.js
Normal file
318
popup.js
Normal file
@@ -0,0 +1,318 @@
|
||||
// Popup script for Page Editor extension
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initializePopup();
|
||||
});
|
||||
|
||||
function initializePopup() {
|
||||
const toggle = document.getElementById('editModeToggle');
|
||||
|
||||
// Check current edit mode state
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
const currentTab = tabs[0];
|
||||
const pageUrl = currentTab.url;
|
||||
const EDIT_MODE_KEY = `edit_mode_${btoa(pageUrl)}`;
|
||||
|
||||
chrome.storage.session.get([EDIT_MODE_KEY], (result) => {
|
||||
const isEnabled = !!result[EDIT_MODE_KEY];
|
||||
|
||||
// Update toggle UI
|
||||
if (isEnabled) {
|
||||
toggle.classList.add('enabled');
|
||||
document.getElementById('statusText').textContent = 'Enabled';
|
||||
document.getElementById('statusText').classList.add('active');
|
||||
document.getElementById('editInfo').classList.add('visible');
|
||||
document.getElementById('keyboardShortcuts').classList.add('visible');
|
||||
document.getElementById('editActionsSection').style.display = 'block';
|
||||
} else {
|
||||
toggle.classList.remove('enabled');
|
||||
document.getElementById('statusText').textContent = 'Disabled';
|
||||
document.getElementById('statusText').classList.remove('active');
|
||||
document.getElementById('editInfo').classList.remove('visible');
|
||||
document.getElementById('keyboardShortcuts').classList.remove('visible');
|
||||
document.getElementById('editActionsSection').style.display = 'none';
|
||||
}
|
||||
|
||||
updateEditStatus();
|
||||
});
|
||||
});
|
||||
|
||||
// Toggle click handler
|
||||
toggle.addEventListener('click', () => {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
const currentTab = tabs[0];
|
||||
const pageUrl = currentTab.url;
|
||||
const EDIT_MODE_KEY = `edit_mode_${btoa(pageUrl)}`;
|
||||
|
||||
// Get current state
|
||||
chrome.storage.session.get([EDIT_MODE_KEY], (result) => {
|
||||
const isCurrentlyEnabled = !!result[EDIT_MODE_KEY];
|
||||
const newState = !isCurrentlyEnabled;
|
||||
|
||||
// Store new state
|
||||
if (newState) {
|
||||
chrome.storage.session.set({ [EDIT_MODE_KEY]: true });
|
||||
} else {
|
||||
chrome.storage.session.remove([EDIT_MODE_KEY]);
|
||||
}
|
||||
|
||||
// Send message to content script
|
||||
chrome.tabs.sendMessage(currentTab.id, {
|
||||
action: 'toggleEditMode',
|
||||
enabled: newState
|
||||
}, (response) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.log('Content script not ready:', chrome.runtime.lastError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update UI
|
||||
if (newState) {
|
||||
toggle.classList.add('enabled');
|
||||
document.getElementById('statusText').textContent = 'Enabled';
|
||||
document.getElementById('statusText').classList.add('active');
|
||||
document.getElementById('editInfo').classList.add('visible');
|
||||
document.getElementById('keyboardShortcuts').classList.add('visible');
|
||||
document.getElementById('editActionsSection').style.display = 'block';
|
||||
} else {
|
||||
toggle.classList.remove('enabled');
|
||||
document.getElementById('statusText').textContent = 'Disabled';
|
||||
document.getElementById('statusText').classList.remove('active');
|
||||
document.getElementById('editInfo').classList.remove('visible');
|
||||
document.getElementById('keyboardShortcuts').classList.remove('visible');
|
||||
document.getElementById('editActionsSection').style.display = 'none';
|
||||
}
|
||||
|
||||
updateEditStatus();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Attach button listeners
|
||||
attachButtonListeners();
|
||||
}
|
||||
|
||||
function attachButtonListeners() {
|
||||
// Save All button
|
||||
const saveAllBtn = document.getElementById('saveAllBtn');
|
||||
if (saveAllBtn) {
|
||||
saveAllBtn.addEventListener('click', () => {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
chrome.scripting.executeScript({
|
||||
target: { tabId: tabs[0].id },
|
||||
function: () => {
|
||||
const editableElements = document.querySelectorAll('[data-editable-marked]');
|
||||
const edits = {};
|
||||
|
||||
// Manually save (calling saveAllEdits from content script)
|
||||
editableElements.forEach((el) => {
|
||||
if (el.textContent.trim().length > 0) {
|
||||
console.log('[Page Editor] Saving element');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
updateEditStatus();
|
||||
showMessage('All changes saved!', 'success');
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Clear All button
|
||||
const clearAllBtn = document.getElementById('clearAllBtn');
|
||||
if (clearAllBtn) {
|
||||
clearAllBtn.addEventListener('click', () => {
|
||||
if (confirm('Are you sure you want to clear all edits on this page? This cannot be undone.')) {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
const pageUrl = tabs[0].url;
|
||||
const storageKey = `page_edits_${btoa(pageUrl)}`;
|
||||
|
||||
chrome.storage.local.remove([storageKey], () => {
|
||||
chrome.scripting.executeScript({
|
||||
target: { tabId: tabs[0].id },
|
||||
function: () => {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
updateEditStatus();
|
||||
showMessage('All edits cleared!', 'success');
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Export button
|
||||
const exportBtn = document.getElementById('exportBtn');
|
||||
if (exportBtn) {
|
||||
exportBtn.addEventListener('click', () => {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
const pageUrl = tabs[0].url;
|
||||
const storageKey = `page_edits_${btoa(pageUrl)}`;
|
||||
|
||||
chrome.storage.local.get([storageKey], (result) => {
|
||||
const edits = result[storageKey] || {};
|
||||
|
||||
if (Object.keys(edits).length === 0) {
|
||||
showMessage('No edits to export', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const exportData = {
|
||||
url: pageUrl,
|
||||
exportedAt: new Date().toISOString(),
|
||||
edits: edits
|
||||
};
|
||||
|
||||
const dataStr = JSON.stringify(exportData, null, 2);
|
||||
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(dataBlob);
|
||||
const filename = `page_edits_${Date.now()}.json`;
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
link.click();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
showMessage('Edits exported!', 'success');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateEditStatus() {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
const currentTab = tabs[0];
|
||||
const pageUrl = currentTab.url;
|
||||
const storageKey = `page_edits_${btoa(pageUrl)}`;
|
||||
|
||||
chrome.storage.local.get([storageKey], (result) => {
|
||||
const edits = result[storageKey] || {};
|
||||
const editCount = Object.keys(edits).length;
|
||||
|
||||
const statusEl = document.getElementById('editStatus');
|
||||
if (editCount === 0) {
|
||||
statusEl.textContent = 'No edits yet on this page';
|
||||
} else {
|
||||
statusEl.innerHTML = `<span id="editCount">${editCount}</span> element${editCount !== 1 ? 's' : ''} edited`;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showMessage(message, type = 'info') {
|
||||
const statusEl = document.getElementById('editStatus');
|
||||
const originalText = statusEl.textContent;
|
||||
|
||||
statusEl.textContent = message;
|
||||
statusEl.style.color = type === 'success' ? '#2e7d32' : '#d32f2f';
|
||||
|
||||
setTimeout(() => {
|
||||
statusEl.textContent = originalText;
|
||||
statusEl.style.color = '#666';
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function attachButtonListeners() {
|
||||
// Save All button
|
||||
document.getElementById('saveAllBtn').addEventListener('click', () => {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
chrome.scripting.executeScript({
|
||||
target: { tabId: tabs[0].id },
|
||||
function: () => {
|
||||
// Call the saveAllEdits function from content script
|
||||
const event = new Event('save-all-edits');
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
updateStatus();
|
||||
showMessage('All changes saved!', 'success');
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
|
||||
// Clear All button
|
||||
document.getElementById('clearAllBtn').addEventListener('click', () => {
|
||||
if (confirm('Are you sure you want to clear all edits on this page? This cannot be undone.')) {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
const pageUrl = tabs[0].url;
|
||||
const storageKey = `page_edits_${btoa(pageUrl)}`;
|
||||
|
||||
chrome.storage.local.remove([storageKey], () => {
|
||||
chrome.scripting.executeScript({
|
||||
target: { tabId: tabs[0].id },
|
||||
function: () => {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
updateStatus();
|
||||
showMessage('All edits cleared!', 'success');
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Export button
|
||||
document.getElementById('exportBtn').addEventListener('click', () => {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
const pageUrl = tabs[0].url;
|
||||
const storageKey = `page_edits_${btoa(pageUrl)}`;
|
||||
|
||||
chrome.storage.local.get([storageKey], (result) => {
|
||||
const edits = result[storageKey] || {};
|
||||
|
||||
if (Object.keys(edits).length === 0) {
|
||||
showMessage('No edits to export', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const exportData = {
|
||||
url: pageUrl,
|
||||
exportedAt: new Date().toISOString(),
|
||||
edits: edits
|
||||
};
|
||||
|
||||
const dataStr = JSON.stringify(exportData, null, 2);
|
||||
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(dataBlob);
|
||||
const filename = `page_edits_${Date.now()}.json`;
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
link.click();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
showMessage('Edits exported!', 'success');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showMessage(message, type = 'info') {
|
||||
const statusEl = document.getElementById('status');
|
||||
const originalText = statusEl.textContent;
|
||||
const originalClass = statusEl.className;
|
||||
|
||||
statusEl.textContent = message;
|
||||
statusEl.className = `status ${type === 'success' ? 'success' : 'error'}`;
|
||||
|
||||
setTimeout(() => {
|
||||
statusEl.textContent = originalText;
|
||||
statusEl.className = originalClass;
|
||||
}, 2000);
|
||||
}
|
||||
49
styles.css
Normal file
49
styles.css
Normal file
@@ -0,0 +1,49 @@
|
||||
/* No visual styling - edited elements look normal */
|
||||
[contenteditable="true"] {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.editable-text {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Auto-save indicator */
|
||||
.page-editor-indicator {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background-color: #4285f4;
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
z-index: 999999;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
/* Save notification */
|
||||
.page-editor-notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
z-index: 999999;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
opacity: 0;
|
||||
display: none;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
/* Hide edit elements in print */
|
||||
@media print {
|
||||
[contenteditable="true"] {
|
||||
outline: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user