Character.ai Text Highlighter and Note Taker

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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');
})();