【油猴脚本】马克笔

// ==UserScript==
// @name         Text Highlighter
// @namespace    https://beiduofen.top/
// @version      0.2
// @description  Highlight text on webpages and save to IndexedDB
// @author       Steper Lin
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Constants
    const DB_NAME = 'HighlighterDB';
    const DB_VERSION = 1;
    const STORE_NAME = 'highlights';
    const HIGHLIGHT_CLASS = 'tampermonkey-highlight';
    const MARKER_ID = 'text-marker-button';

    // Variables
    let db;
    let currentSelection = null;
    let markerButton = null;

    // Initialize the database
    function initDB() {
        return new Promise((resolve, reject) => {
            console.log('Initializing IndexedDB database:', DB_NAME);

            // Close any existing database connection
            if (db) {
                console.log('Closing existing database connection');
                db.close();
                db = null;
            }

            // Open the database
            const request = indexedDB.open(DB_NAME, DB_VERSION);

            request.onupgradeneeded = function(event) {
                console.log('Database upgrade needed, creating object store');
                const database = event.target.result;

                // Create the object store if it doesn't exist
                if (!database.objectStoreNames.contains(STORE_NAME)) {
                    const store = database.createObjectStore(STORE_NAME, { keyPath: 'id' });
                    console.log('Created object store:', STORE_NAME);

                    // Create indexes for faster lookups
                    store.createIndex('url', 'url', { unique: false });
                    store.createIndex('timestamp', 'timestamp', { unique: false });
                    console.log('Created indexes for url and timestamp');
                } else {
                    console.log('Object store already exists');
                }
            };

            request.onsuccess = function(event) {
                db = event.target.result;
                console.log('Database initialized successfully');

                // Set up error handling for the database connection
                db.onerror = function(event) {
                    console.error('Database error:', event.target.error);
                };

                // Verify the database is working by checking the object store
                try {
                    const transaction = db.transaction([STORE_NAME], 'readonly');
                    const store = transaction.objectStore(STORE_NAME);
                    console.log('Database verification successful');

                    // Get the count of highlights
                    const countRequest = store.count();
                    countRequest.onsuccess = function() {
                        console.log(`Database contains ${countRequest.result} highlights`);
                    };
                } catch (error) {
                    console.error('Database verification failed:', error);
                    // Try to recreate the database
                    db.close();
                    indexedDB.deleteDatabase(DB_NAME);
                    setTimeout(() => {
                        const retryRequest = indexedDB.open(DB_NAME, DB_VERSION + 1);
                        retryRequest.onupgradeneeded = function(event) {
                            const database = event.target.result;
                            const store = database.createObjectStore(STORE_NAME, { keyPath: 'id' });
                            store.createIndex('url', 'url', { unique: false });
                            store.createIndex('timestamp', 'timestamp', { unique: false });
                            console.log('Recreated database after verification failure');
                        };
                        retryRequest.onsuccess = function(event) {
                            db = event.target.result;
                            console.log('Database recreated successfully');
                            resolve(db);
                        };
                        retryRequest.onerror = function(event) {
                            console.error('Error recreating database:', event.target.error);
                            reject(event.target.error);
                        };
                    }, 100);
                    return;
                }

                resolve(db);
            };

            request.onerror = function(event) {
                console.error('Error initializing database:', event.target.error);

                // Try to use localStorage as a fallback
                console.log('Attempting to use localStorage as fallback');
                try {
                    // Create a mock IndexedDB API using localStorage
                    db = {
                        usingLocalStorage: true,
                        getAll: function() {
                            try {
                                const data = localStorage.getItem(`${DB_NAME}_${STORE_NAME}`);
                                return data ? JSON.parse(data) : [];
                            } catch (e) {
                                console.error('Error reading from localStorage:', e);
                                return [];
                            }
                        },
                        add: function(item) {
                            try {
                                const data = this.getAll();
                                item.id = item.id || `highlight-${Date.now()}-${Math.floor(Math.random() * 1000000)}`;
                                data.push(item);
                                localStorage.setItem(`${DB_NAME}_${STORE_NAME}`, JSON.stringify(data));
                                return item.id;
                            } catch (e) {
                                console.error('Error writing to localStorage:', e);
                                throw e;
                            }
                        }
                    };
                    console.log('Using localStorage fallback');
                    resolve(db);
                } catch (fallbackError) {
                    console.error('Fallback to localStorage failed:', fallbackError);
                    reject(event.target.error);
                }
            };

            // Handle blocked events (another connection is blocking this one)
            request.onblocked = function(event) {
                console.warn('Database connection blocked, please close other tabs with this application');
                alert('Please close other tabs with the Text Highlighter to allow database access');
            };
        });
    }

    // Save highlight to IndexedDB
    function saveHighlight(highlight) {
        return new Promise((resolve, reject) => {
            console.log('Saving highlight to database:', highlight);

            // Make sure we have a database connection
            if (!db) {
                console.error('No database connection available');
                reject(new Error('No database connection available'));
                return;
            }

            // Handle localStorage fallback
            if (db.usingLocalStorage) {
                try {
                    const id = db.add(highlight);
                    console.log('Highlight saved to localStorage with ID:', id);
                    resolve(id);
                } catch (error) {
                    console.error('Error saving to localStorage:', error);
                    reject(error);
                }
                return;
            }

            // Normal IndexedDB operation
            try {
                const transaction = db.transaction([STORE_NAME], 'readwrite');

                transaction.oncomplete = function() {
                    console.log('Transaction completed successfully');
                };

                transaction.onerror = function(event) {
                    console.error('Transaction error:', event.target.error);
                    reject(event.target.error);
                };

                const store = transaction.objectStore(STORE_NAME);

                // Make sure the highlight has an ID
                if (!highlight.id) {
                    highlight.id = generateHighlightId();
                }

                // Add timestamp if not present
                if (!highlight.timestamp) {
                    highlight.timestamp = Date.now();
                }

                const request = store.add(highlight);

                request.onsuccess = function(event) {
                    console.log('Highlight saved successfully with ID:', event.target.result);
                    resolve(event.target.result);
                };

                request.onerror = function(event) {
                    console.error('Error saving highlight:', event.target.error);

                    // Try to update if the item already exists
                    if (event.target.error.name === 'ConstraintError') {
                        console.log('Highlight already exists, trying to update');
                        const updateRequest = store.put(highlight);

                        updateRequest.onsuccess = function(event) {
                            console.log('Highlight updated successfully');
                            resolve(highlight.id);
                        };

                        updateRequest.onerror = function(event) {
                            console.error('Error updating highlight:', event.target.error);
                            reject(event.target.error);
                        };
                    } else {
                        reject(event.target.error);
                    }
                };
            } catch (error) {
                console.error('Error in saveHighlight:', error);
                reject(error);
            }
        });
    }

    // Get all highlights from IndexedDB
    function getAllHighlights() {
        return new Promise((resolve, reject) => {
            console.log('Getting all highlights from database');

            // Make sure we have a database connection
            if (!db) {
                console.error('No database connection available');
                reject(new Error('No database connection available'));
                return;
            }

            // Handle localStorage fallback
            if (db.usingLocalStorage) {
                try {
                    const highlights = db.getAll();
                    console.log('Retrieved highlights from localStorage:', highlights);
                    resolve(highlights || []);
                } catch (error) {
                    console.error('Error getting highlights from localStorage:', error);
                    reject(error);
                }
                return;
            }

            // Normal IndexedDB operation
            try {
                const transaction = db.transaction([STORE_NAME], 'readonly');
                const store = transaction.objectStore(STORE_NAME);

                // Try to use the url index if we're looking for highlights for the current page
                const currentUrl = window.location.href.split('#')[0].split('?')[0];
                let request;

                try {
                    // Check if the index exists
                    const urlIndex = store.index('url');
                    console.log('Using URL index for faster retrieval');

                    // Get all highlights for all URLs (we'll filter later)
                    request = store.getAll();
                } catch (indexError) {
                    console.warn('URL index not available, falling back to getAll()');
                    request = store.getAll();
                }

                request.onsuccess = function(event) {
                    const highlights = event.target.result || [];
                    console.log(`Retrieved ${highlights.length} highlights from database`);
                    resolve(highlights);
                };

                request.onerror = function(event) {
                    console.error('Error getting highlights:', event.target.error);
                    reject(event.target.error);
                };
            } catch (error) {
                console.error('Error in getAllHighlights:', error);
                reject(error);
            }
        });
    }

    // Create a marker button
    function createMarkerButton() {
        // Remove existing button if it exists
        const existingButton = document.getElementById(MARKER_ID);
        if (existingButton) {
            document.body.removeChild(existingButton);
        }

        // Create new button
        const button = document.createElement('div');
        button.id = MARKER_ID;
        button.innerHTML = '🖌️';
        button.style.position = 'absolute';
        button.style.zIndex = '9999';
        button.style.backgroundColor = '#ffff00';
        button.style.border = '1px solid #ccc';
        button.style.borderRadius = '50%';
        button.style.width = '30px';
        button.style.height = '30px';
        button.style.display = 'none'; // Start hidden
        button.style.justifyContent = 'center';
        button.style.alignItems = 'center';
        button.style.cursor = 'pointer';
        button.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
        button.style.fontSize = '16px';
        button.style.transition = 'transform 0.2s';

        button.addEventListener('mouseover', () => {
            button.style.transform = 'scale(1.1)';
        });

        button.addEventListener('mouseout', () => {
            button.style.transform = 'scale(1)';
        });

        // Use mousedown instead of click to prevent selection loss
        button.addEventListener('mousedown', (e) => {
            e.preventDefault();
            e.stopPropagation();
            handleMarkerClick(e);
        });

        document.body.appendChild(button);
        return button;
    }

    // Position the marker button near the selection
    function positionMarkerButton(selection) {
        if (!markerButton) {
            markerButton = createMarkerButton();
        }

        try {
            if (selection.rangeCount === 0) {
                console.error('No range in selection');
                return;
            }

            const range = selection.getRangeAt(0);
            const rect = range.getBoundingClientRect();

            // Position the button near the end of the selection
            markerButton.style.left = `${window.scrollX + rect.right + 5}px`;
            markerButton.style.top = `${window.scrollY + rect.top - 5}px`;

            // Make sure the button is visible
            markerButton.style.display = 'flex';

            // Make sure the button is within viewport
            const buttonRect = markerButton.getBoundingClientRect();
            const viewportWidth = window.innerWidth;
            const viewportHeight = window.innerHeight;

            if (buttonRect.right > viewportWidth) {
                // If button is off the right edge, move it to the left
                markerButton.style.left = `${window.scrollX + viewportWidth - buttonRect.width - 10}px`;
            }

            if (buttonRect.bottom > viewportHeight) {
                // If button is off the bottom edge, move it up
                markerButton.style.top = `${window.scrollY + viewportHeight - buttonRect.height - 10}px`;
            }

            if (buttonRect.top < 0) {
                // If button is off the top edge, move it down
                markerButton.style.top = `${window.scrollY + 10}px`;
            }

            if (buttonRect.left < 0) {
                // If button is off the left edge, move it right
                markerButton.style.left = `${window.scrollX + 10}px`;
            }
        } catch (error) {
            console.error('Error positioning marker button:', error);
        }
    }

    // Hide the marker button
    function hideMarkerButton() {
        if (markerButton) {
            markerButton.style.display = 'none';
        }
    }

    // Handle marker button click
    function handleMarkerClick(event) {
        // Prevent the default action and stop propagation
        if (event) {
            event.preventDefault();
            event.stopPropagation();
        }

        if (currentSelection) {
            // Clone the selection to ensure it doesn't get lost
            const selectionText = currentSelection.toString();

            // Highlight the selection
            highlightSelection(currentSelection);

            // Hide the marker button
            hideMarkerButton();

            // Clear the current selection
            currentSelection = null;

            // Show a brief notification
            showNotification(`Highlighted: "${selectionText.substring(0, 20)}${selectionText.length > 20 ? '...' : ''}"`);
        }

        return false;
    }

    // Show a brief notification
    function showNotification(message, duration = 2000) {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.position = 'fixed';
        notification.style.bottom = '10px';
        notification.style.right = '10px';
        notification.style.backgroundColor = 'rgba(255, 255, 0, 0.8)';
        notification.style.padding = '5px 10px';
        notification.style.borderRadius = '5px';
        notification.style.zIndex = '10000';
        notification.style.fontSize = '12px';
        notification.style.fontWeight = 'bold';
        notification.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
        document.body.appendChild(notification);

        // Remove the notification after the specified duration
        setTimeout(() => {
            if (document.body.contains(notification)) {
                document.body.removeChild(notification);
            }
        }, duration);
    }

    // Generate a unique ID for the highlight
    function generateHighlightId() {
        return `highlight-${Date.now()}-${Math.floor(Math.random() * 1000000)}`;
    }

    // Highlight the selected text
    function highlightSelection(selection) {
        try {
            if (!selection || selection.rangeCount === 0) {
                return;
            }

            const range = selection.getRangeAt(0);
            const highlightId = generateHighlightId();

            // Method 1: Simple approach for single node selections
            if (range.startContainer === range.endContainer) {
                // Create a span element to wrap the selected text
                const highlightSpan = document.createElement('span');
                highlightSpan.className = HIGHLIGHT_CLASS;
                highlightSpan.dataset.highlightId = highlightId;
                highlightSpan.style.backgroundColor = 'yellow';
                highlightSpan.style.color = 'black';

                try {
                    range.surroundContents(highlightSpan);

                    // Save the highlight to IndexedDB
                    const highlightData = {
                        id: highlightId,
                        text: highlightSpan.textContent,
                        url: window.location.href,
                        path: getXPath(highlightSpan),
                        timestamp: Date.now()
                    };

                    saveHighlight(highlightData);
                    return;
                } catch (error) {
                    // Fall through to method 2
                }
            }

            // Method 2: More complex approach for multi-node selections
            // Create a document fragment from the selection
            const fragment = range.cloneContents();

            // Create a temporary div to hold the fragment
            const tempDiv = document.createElement('div');
            tempDiv.appendChild(fragment);

            // Wrap the content with highlight spans
            const wrappedHTML = `<span class="${HIGHLIGHT_CLASS}" data-highlight-id="${highlightId}" style="background-color: yellow; color: black;">${tempDiv.innerHTML}</span>`;

            // Delete the original content
            range.deleteContents();

            // Insert the new highlighted content
            const tempElement = document.createElement('div');
            tempElement.innerHTML = wrappedHTML;

            // Insert each child of the temp element
            while (tempElement.firstChild) {
                range.insertNode(tempElement.firstChild);
            }

            // Save the highlight to IndexedDB
            const highlightData = {
                id: highlightId,
                text: selection.toString(),
                url: window.location.href,
                selectionHTML: wrappedHTML,
                timestamp: Date.now()
            };

            saveHighlight(highlightData);

        } catch (error) {
            console.error('Error in highlightSelection function:', error);
        }
    }

    // Get XPath for an element
    function getXPath(element) {
        if (element.id !== '') {
            return `//*[@id="${element.id}"]`;
        }

        if (element === document.body) {
            return '/html/body';
        }

        try {
            let ix = 0;
            const siblings = element.parentNode.childNodes;

            for (let i = 0; i < siblings.length; i++) {
                const sibling = siblings[i];

                if (sibling === element) {
                    const parentXPath = getXPath(element.parentNode);
                    return `${parentXPath}/${element.tagName.toLowerCase()}[${ix + 1}]`;
                }

                if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
                    ix++;
                }
            }

            return null;
        } catch (error) {
            console.error('Error generating XPath:', error);
            return null;
        }
    }

    // Find element by XPath
    function getElementByXPath(path) {
        try {
            return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
        } catch (error) {
            console.error('Error finding element by XPath:', error);
            return null;
        }
    }

    // Apply highlights from IndexedDB
    async function applyHighlights() {
        try {
            console.log('Applying highlights from IndexedDB');
            const highlights = await getAllHighlights();
            console.log('Retrieved highlights from database:', highlights);

            // Filter highlights for the current page
            const currentUrl = window.location.href;
            console.log('Current URL:', currentUrl);

            // Match by URL without hash or query parameters for better matching
            const currentUrlBase = currentUrl.split('#')[0].split('?')[0];
            console.log('Base URL for matching:', currentUrlBase);

            const pageHighlights = highlights.filter(h => {
                const highlightUrl = h.url.split('#')[0].split('?')[0];
                return highlightUrl === currentUrlBase;
            });

            console.log(`Found ${pageHighlights.length} highlights for this page:`, pageHighlights);

            if (pageHighlights.length === 0) {
                console.log('No highlights to apply');
                return;
            }

            // Wait for the page to be fully loaded
            if (document.readyState !== 'complete') {
                console.log('Page not fully loaded, waiting...');
                await new Promise(resolve => {
                    window.addEventListener('load', resolve);
                    // Fallback if load event already fired
                    setTimeout(resolve, 1000);
                });
                console.log('Page loaded, proceeding with highlight application');
            }

            // Apply each highlight
            let appliedCount = 0;

            for (const highlight of pageHighlights) {
                try {
                    console.log('Applying highlight:', highlight);
                    let applied = false;

                    // Method 1: If we have a path, try to find the element by XPath
                    if (highlight.path) {
                        console.log('Trying to apply using XPath:', highlight.path);
                        const element = getElementByXPath(highlight.path);

                        if (element) {
                            console.log('Found element by XPath:', element);

                            // Create a highlight span
                            const highlightSpan = document.createElement('span');
                            highlightSpan.className = HIGHLIGHT_CLASS;
                            highlightSpan.dataset.highlightId = highlight.id;
                            highlightSpan.style.backgroundColor = 'yellow';
                            highlightSpan.style.color = 'black';

                            // Replace the text with the highlighted version
                            const textNode = document.createTextNode(element.textContent);
                            element.innerHTML = '';
                            element.appendChild(textNode);

                            try {
                                // Wrap the text with the highlight span
                                const range = document.createRange();
                                range.selectNodeContents(element);
                                range.surroundContents(highlightSpan);
                                console.log('Successfully applied highlight using XPath');
                                applied = true;
                                appliedCount++;
                                continue; // Success, move to next highlight
                            } catch (error) {
                                console.error('Error applying highlight with surroundContents:', error);
                                // Continue to next method
                            }
                        } else {
                            console.warn('Element not found by XPath');
                        }
                    }

                    // Method 2: If we have text, search for it in the document
                    if (highlight.text && !applied) {
                        console.log('Trying to apply using text search:', highlight.text);

                        // Search for the text in the document
                        const textToFind = highlight.text;
                        if (!textToFind || textToFind.length < 5) {
                            console.warn('Text too short to reliably find:', textToFind);
                            continue;
                        }

                        // Create a TreeWalker to iterate through text nodes
                        const walker = document.createTreeWalker(
                            document.body,
                            NodeFilter.SHOW_TEXT,
                            null,
                            false
                        );

                        let node;
                        let found = false;

                        while ((node = walker.nextNode())) {
                            if (node.nodeValue.includes(textToFind)) {
                                console.log('Found text in node:', node);

                                // Create a range around the text
                                const range = document.createRange();
                                const startIndex = node.nodeValue.indexOf(textToFind);
                                range.setStart(node, startIndex);
                                range.setEnd(node, startIndex + textToFind.length);

                                // Create a highlight span
                                const highlightSpan = document.createElement('span');
                                highlightSpan.className = HIGHLIGHT_CLASS;
                                highlightSpan.dataset.highlightId = highlight.id;
                                highlightSpan.style.backgroundColor = 'yellow';
                                highlightSpan.style.color = 'black';

                                try {
                                    // Extract the content and wrap it in the highlight span
                                    const fragment = range.extractContents();
                                    highlightSpan.appendChild(fragment);
                                    range.insertNode(highlightSpan);
                                    console.log('Successfully applied highlight using text search');
                                    found = true;
                                    applied = true;
                                    appliedCount++;
                                    break;
                                } catch (error) {
                                    console.error('Error applying highlight with text search:', error);
                                    // Continue to next node
                                }
                            }
                        }

                        if (!found) {
                            console.warn('Could not find text in document:', textToFind);
                        }
                    }

                    // Method 3: If we have selectionHTML, use that
                    if (highlight.selectionHTML && !applied) {
                        console.log('Trying to apply using selectionHTML');

                        // Create a temporary element with the highlight HTML
                        const tempDiv = document.createElement('div');
                        tempDiv.innerHTML = highlight.selectionHTML;

                        // Extract the text content
                        const textToFind = tempDiv.textContent.trim();
                        if (!textToFind || textToFind.length < 5) {
                            console.warn('HTML content too short to reliably find');
                            continue;
                        }

                        console.log('Searching for HTML content text:', textToFind);

                        // Create a TreeWalker to iterate through text nodes
                        const walker = document.createTreeWalker(
                            document.body,
                            NodeFilter.SHOW_TEXT,
                            null,
                            false
                        );

                        let node;
                        let found = false;

                        while ((node = walker.nextNode())) {
                            if (node.nodeValue.includes(textToFind)) {
                                console.log('Found HTML content text in node:', node);

                                // Create a range around the text
                                const range = document.createRange();
                                const startIndex = node.nodeValue.indexOf(textToFind);
                                range.setStart(node, startIndex);
                                range.setEnd(node, startIndex + textToFind.length);

                                // Get the highlight span from the temp div
                                const highlightSpan = tempDiv.querySelector(`.${HIGHLIGHT_CLASS}`) || tempDiv.firstChild;

                                try {
                                    // Delete the original content and insert the highlight
                                    range.deleteContents();
                                    range.insertNode(highlightSpan);
                                    console.log('Successfully applied highlight using HTML content');
                                    found = true;
                                    applied = true;
                                    appliedCount++;
                                    break;
                                } catch (error) {
                                    console.error('Error applying highlight with HTML content:', error);
                                    // Continue to next node
                                }
                            }
                        }

                        if (!found) {
                            console.warn('Could not find HTML content in document');
                        }
                    }

                    if (!applied) {
                        console.warn(`Could not apply highlight ${highlight.id} using any method`);
                    }
                } catch (error) {
                    console.error(`Error applying highlight ${highlight.id}:`, error);
                }
            }

            // Show notification if highlights were applied
            if (appliedCount > 0) {
                console.log(`Successfully applied ${appliedCount} highlights`);
                showNotification(`Applied ${appliedCount} highlight${appliedCount === 1 ? '' : 's'}`);
            } else if (pageHighlights.length > 0) {
                console.warn('Failed to apply any highlights');
                showNotification('Failed to apply highlights', 3000);
            }
        } catch (error) {
            console.error('Error applying highlights:', error);
        }
    }

    // Handle text selection
    function handleSelection(event) {
        // Short delay to allow selection to complete
        setTimeout(() => {
            const selection = window.getSelection();
            const selectionText = selection.toString().trim();

            if (selectionText !== '') {
                // Store the selection
                currentSelection = selection;

                // Position and show the marker button
                positionMarkerButton(selection);
            } else {
                // Only hide the marker if the click wasn't on the marker itself
                if (event && event.target && event.target.id !== MARKER_ID) {
                    currentSelection = null;
                    hideMarkerButton();
                }
            }
        }, 10); // Small delay to ensure selection is complete
    }

    // Add CSS styles
    function addStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .${HIGHLIGHT_CLASS} {
                background-color: yellow;
                color: black;
            }
        `;
        document.head.appendChild(style);
    }

    // Initialize the script
    async function init() {
        try {
            console.log('Initializing Text Highlighter script');

            // Add styles first
            addStyles();
            console.log('Styles added');

            // Create the marker button early
            markerButton = createMarkerButton();
            console.log('Marker button initialized');

            // Initialize the database
            await initDB();
            console.log('Database initialized successfully');

            // Wait for the DOM to be fully loaded
            if (document.readyState !== 'complete' && document.readyState !== 'interactive') {
                console.log('Waiting for DOM to be ready...');
                await new Promise(resolve => {
                    document.addEventListener('DOMContentLoaded', resolve);
                    // Fallback if event already fired
                    setTimeout(resolve, 1000);
                });
                console.log('DOM is ready');
            }

            // Apply existing highlights with a slight delay to ensure DOM is fully processed
            console.log('Scheduling highlight application');
            setTimeout(async () => {
                try {
                    await applyHighlights();
                    console.log('Highlights applied');
                } catch (error) {
                    console.error('Error applying highlights:', error);
                }
            }, 500);

            // Listen for text selection events
            document.addEventListener('mouseup', handleSelection);
            document.addEventListener('mousedown', (e) => {
                // If clicking on the marker button, prevent default to keep selection
                if (e.target && e.target.id === MARKER_ID) {
                    e.preventDefault();
                    e.stopPropagation();
                }
            });
            document.addEventListener('selectionchange', () => {
                const selection = window.getSelection();
                if (selection && selection.toString().trim() !== '') {
                    currentSelection = selection;
                }
            });
            console.log('Event listeners added');

            // Add mutation observer to handle dynamic content
            const observer = new MutationObserver((mutations) => {
                // If significant DOM changes occurred, reapply highlights
                const significantChanges = mutations.some(mutation =>
                    mutation.type === 'childList' && mutation.addedNodes.length > 3);

                if (significantChanges) {
                    console.log('Significant DOM changes detected, reapplying highlights');
                    setTimeout(() => applyHighlights(), 500);
                }
            });

            // Start observing the document with the configured parameters
            observer.observe(document.body, { childList: true, subtree: true });
            console.log('Mutation observer started');

            // Show initialization notification
            showNotification('Text Highlighter Active', 2000);
            console.log('Text Highlighter initialized successfully');
        } catch (error) {
            console.error('Error initializing Text Highlighter:', error);
            // Try to recover
            try {
                if (!db) {
                    console.log('Attempting to recover by reinitializing database');
                    await initDB();
                }
                if (!markerButton) {
                    console.log('Recreating marker button');
                    markerButton = createMarkerButton();
                }
            } catch (recoveryError) {
                console.error('Recovery failed:', recoveryError);
            }
        }
    }

    // Start the script
    init();
})();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

步子哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值