Character.ai Text Highlighter and Note Taker

Advanced text highlighting and note-taking for Character.ai with single-click and un-highlighting.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Character.ai Text Highlighter and Note Taker
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  Advanced text highlighting and note-taking for Character.ai with single-click and un-highlighting.
// @match        https://character.ai/*
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    // Enhanced styling for highlights and notes
    const styleElement = document.createElement('style');
    styleElement.textContent = `
        .ca-highlight {
            background-color: yellow;
            color: black;
            cursor: pointer;
            transition: background-color 0.2s ease;
            display: inline; /* Ensure inline display for proper spacing */
            white-space: pre-wrap; /* Preserve whitespace */
        }
        .ca-highlight:hover {
            background-color: #ffff99;
        }
        .ca-note-popup {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: #1a1a1a;
            border: 2px solid #333;
            border-radius: 8px;
            padding: 20px;
            width: 350px;
            z-index: 10000;
            box-shadow: 0 4px 6px rgba(0,0,0,0.5);
        }
        .ca-note-input {
            width: 100%;
            margin-bottom: 10px;
            padding: 8px;
            background-color: #2a2a2a;
            color: white;
            border: 1px solid #444;
            border-radius: 4px;
            resize: vertical;
            min-height: 100px;
        }
        .ca-note-buttons {
            display: flex;
            justify-content: space-between;
        }
        .ca-note-save {
            background-color: #4CAF50;
            color: white;
            border: none;
            padding: 10px 15px;
            border-radius: 4px;
            cursor: pointer;
            margin-right: 10px;
        }
        .ca-note-cancel {
            background-color: #f44336;
            color: white;
            border: none;
            padding: 10px 15px;
            border-radius: 4px;
            cursor: pointer;
        }
        .ca-highlight-mode-indicator {
            position: fixed;
            top: 10px;
            right: 10px;
            background-color: yellow;
            color: black;
            padding: 5px 10px;
            border-radius: 4px;
            z-index: 9999;
            display: none;
            font-weight: bold;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
        }
        .ca-note-button, .ca-unhighlight-button {
            margin-right: 10px;
            background-color: #555;
            color: white;
            border: none;
            padding: 8px 12px;
            border-radius: 4px;
            cursor: pointer;
        }
        .ca-unhighlight-button {
            background-color: #ff9800;
        }
        .ca-popup-menu {
            position: absolute;
            background-color: #1a1a1a;
            border: 1px solid #333;
            border-radius: 4px;
            padding: 5px;
            z-index: 10001;
            box-shadow: 0 2px 4px rgba(0,0,0,0.3);
        }
    `;
    document.head.appendChild(styleElement);

    // Create highlight mode indicator
    const highlightModeIndicator = document.createElement('div');
    highlightModeIndicator.classList.add('ca-highlight-mode-indicator');
    highlightModeIndicator.textContent = 'Highlight Mode';
    document.body.appendChild(highlightModeIndicator);

    // State variables
    let isHighlightMode = false;
    const STORAGE_KEY = 'caNotes';

    // Utility function to get unique identifier for a word
    function getWordIdentifier(word) {
        return `highlight_${btoa(word.trim())}`;
    }

    // Save note for a word
    function saveNote(word, note) {
        const identifier = getWordIdentifier(word);
        const savedNotes = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
        savedNotes[identifier] = note;
        localStorage.setItem(STORAGE_KEY, JSON.stringify(savedNotes));
    }

    // Retrieve note for a word
    function getNote(word) {
        const identifier = getWordIdentifier(word);
        const savedNotes = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
        return savedNotes[identifier] || '';
    }

    // Create note popup
    function createNotePopup(word) {
        const popup = document.createElement('div');
        popup.classList.add('ca-note-popup');

        const textarea = document.createElement('textarea');
        textarea.classList.add('ca-note-input');
        textarea.placeholder = `Enter note for "${word}"`;
        textarea.value = getNote(word);

        const buttonContainer = document.createElement('div');
        buttonContainer.classList.add('ca-note-buttons');

        const saveButton = document.createElement('button');
        saveButton.textContent = 'Save';
        saveButton.classList.add('ca-note-save');
        saveButton.addEventListener('click', () => {
            saveNote(word, textarea.value);
            document.body.removeChild(popup);
        });

        const cancelButton = document.createElement('button');
        cancelButton.textContent = 'Discard';
        cancelButton.classList.add('ca-note-cancel');
        cancelButton.addEventListener('click', () => {
            document.body.removeChild(popup);
        });

        buttonContainer.appendChild(saveButton);
        buttonContainer.appendChild(cancelButton);

        popup.appendChild(textarea);
        popup.appendChild(buttonContainer);

        return popup;
    }

    // New function to un-highlight text
    function unhighlightText(element) {
        if (element && element.classList.contains('ca-highlight')) {
            // Get the text content
            const text = element.textContent;

            // Create a text node to replace the highlighted span
            const textNode = document.createTextNode(text);

            // Replace the span with the text node
            element.parentNode.replaceChild(textNode, element);
        }
    }

    // Handle highlighting and un-highlighting with a single click
    function handleHighlightClick(e) {
        // Only work in highlight mode
        if (!isHighlightMode) return;

        // Check if clicked on an existing highlight
        if (e.target.classList && e.target.classList.contains('ca-highlight')) {
            // Un-highlight the text
            unhighlightText(e.target);
            return;
        }

        // Otherwise, create a new highlight
        const selection = window.getSelection();
        const selectedText = selection.toString();

        if (selectedText && selectedText.trim() !== '') {
            try {
                // Get the range
                const range = selection.getRangeAt(0);

                // Store the original text with spaces for note-taking
                const originalText = selectedText;

                // Create a highlight span
                const highlightSpan = document.createElement('span');
                highlightSpan.classList.add('ca-highlight');

                // Use createTextNode to preserve spaces
                highlightSpan.appendChild(document.createTextNode(selectedText));

                // Replace selection with our highlight
                range.deleteContents();
                range.insertNode(highlightSpan);

                // Clear the selection
                selection.removeAllRanges();
            } catch (error) {
                console.error('Error highlighting text:', error);
            }
        }
    }

    // Add right-click handling for highlights
    function handleHighlightRightClick(e) {
        // Check if right-clicked on a highlight
        if (e.target.classList && e.target.classList.contains('ca-highlight')) {
            e.preventDefault();

            // Get the highlighted text
            const highlightedText = e.target.textContent;

            // Create and display a popup menu for the note
            const notePopup = createNotePopup(highlightedText);
            document.body.appendChild(notePopup);
        } else {
            // Add note functionality when not in highlight mode
            if (!isHighlightMode) {
                const selection = window.getSelection();
                const selectedText = selection.toString();

                if (selectedText && selectedText.trim() !== '') {
                    e.preventDefault();

                    // Create and display a popup menu for the note
                    const notePopup = createNotePopup(selectedText);
                    document.body.appendChild(notePopup);
                }
            }
        }
    }

    // Toggle highlight mode
    function toggleHighlightMode() {
        isHighlightMode = !isHighlightMode;
        highlightModeIndicator.style.display = isHighlightMode ? 'block' : 'none';

        // Update the indicator text to show the new mode behavior
        highlightModeIndicator.textContent = isHighlightMode ?
            'Highlight Mode (Click to highlight/unhighlight, Right-click for notes)' :
            'Highlight Mode';
    }

    // Event listeners
    document.addEventListener('click', (e) => {
        // Single click for highlighting/unhighlighting when in highlight mode
        handleHighlightClick(e);
    });

    document.addEventListener('contextmenu', (e) => {
        // Right-click for adding notes to highlights
        handleHighlightRightClick(e);
    });

    document.addEventListener('keydown', (e) => {
        // Toggle highlight mode with '/'
        if (e.key === '/') {
            e.preventDefault();
            toggleHighlightMode();
        }
    });

    // Add initial message about feature
    console.log('Character.ai Text Highlighter Loaded:\n- Highlight Mode: "/" to toggle\n- While in highlight mode: click to highlight/unhighlight\n- Right-click on highlighted text to add/view notes\n- Right-click on selected text to add notes when not in highlight mode');
})();