// ==UserScript== // @name c.ai X Text Color // @namespace c.ai X Text Color // @match https://character.ai/* // @grant none // @license MIT // @version 2.8 // @author Vishanka via chatGPT // @description Lets you change the text colors as you wish and highlight chosen words // @icon https://i.imgur.com/ynjBqKW.png // @downloadURL none // ==/UserScript== (function () { var plaintextColor = localStorage.getItem('plaintext_color'); var italicColor = localStorage.getItem('italic_color'); var charbubbleColor = localStorage.getItem('charbubble_color') || '#26272B'; var userbubbleColor = localStorage.getItem('userbubble_color') || '#303136'; // Default color if 'plaintext_color' is not set var defaultColor = '#A2A2AC'; // Use the retrieved color or default color var color = plaintextColor || defaultColor; // Create the CSS style var css = "p[node='[object Object]'] { color: " + color + " !important; font-family: '__Inter_918210','Noto Sans', sans-serif !important; } p, textarea, button, div.text-sm { font-family: '__Inter_918210','Noto Sans', sans-serif !important; } em { color: " + italicColor + " !important; }"; css += `.mt-1.bg-surface-elevation-2 { background-color: ${charbubbleColor}; } .mt-1.bg-surface-elevation-3 { background-color: ${userbubbleColor}; }`; var head = document.getElementsByTagName("head")[0]; var style = document.createElement("style"); style.setAttribute("type", "text/css"); style.innerHTML = css; head.appendChild(style); })(); function changeColors() { const pTags = document.getElementsByTagName("p"); const quotationMarksColor = localStorage.getItem('quotationmarks_color') || '#FFFFFF'; const customColor = localStorage.getItem('custom_color') || '#FFFFFF'; const wordlistCc = JSON.parse(localStorage.getItem('wordlist_cc')) || []; const wordRegex = wordlistCc.length > 0 ? new RegExp('\\b(' + wordlistCc.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') + ')\\b', 'gi') : null; Array.from(pTags).forEach((pTag) => { if ( pTag.dataset.colorChanged === "true" || pTag.querySelector("code") || pTag.querySelector("img") || pTag.querySelector("textarea") || pTag.querySelector("button") || pTag.querySelector("div") ) { return; // Skip iteration } let text = pTag.innerHTML; // Save .katex elements' original HTML and replace with placeholders const katexElems = Array.from(pTag.querySelectorAll(".katex")); const katexReplacements = katexElems.map((elem, index) => { const placeholder = `KATEX_PLACEHOLDER_${index}`; text = text.replace(elem.outerHTML, placeholder); return { html: elem.outerHTML, placeholder }; }); // Handle tags by removing them temporarily and saving their HTML for later restoration const aTags = Array.from(pTag.getElementsByTagName("a")); const aTagsReplacements = aTags.map((aTag, j) => { const placeholder = `REPLACE_ME_${j}`; text = text.replace(aTag.outerHTML, placeholder); return { tag: aTag, placeholder }; }); // Change text within quotation marks and for specific words based on the regex text = text.replace(/(["“”«»].*?["“”«»])/g, `$1`); // text = text.replace(/(["“”«»][^"]*?,["“”«»])/g, `$1`); if (wordRegex) { text = text.replace(wordRegex, `$1`); } // Restore .katex elements and tags [...katexReplacements, ...aTagsReplacements].forEach(({ html, placeholder, tag }) => { text = text.replace(placeholder, html || tag.outerHTML); }); // Update the innerHTML and mark the

tag to avoid re-processing pTag.innerHTML = text; pTag.dataset.colorChanged = "true"; }); console.log("Changed colors"); } const divElements = document.querySelectorAll('div'); divElements.forEach(div => { const observer = new MutationObserver(changeColors); observer.observe(div, { subtree: true, childList: true }); }); function createButton(symbol, onClick) { const colorpalettebutton = document.createElement('button'); colorpalettebutton.innerHTML = symbol; colorpalettebutton.style.position = 'relative'; colorpalettebutton.style.background = 'none'; colorpalettebutton.style.border = 'none'; colorpalettebutton.style.fontSize = '18px'; colorpalettebutton.style.top = '-5px'; colorpalettebutton.style.cursor = 'pointer'; colorpalettebutton.addEventListener('click', onClick); return colorpalettebutton; } // Function to create the color selector panel function createColorPanel() { const panel = document.createElement('div'); panel.id = 'colorPanel'; panel.style.position = 'fixed'; panel.style.top = '50%'; panel.style.left = '50%'; panel.style.transform = 'translate(-50%, -50%)'; const currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light'; if (currentTheme === 'dark') { panel.style.backgroundColor = 'rgba(19, 19, 22, 0.95)'; } else { panel.style.backgroundColor = 'rgba(214, 214, 221, 0.95)'; } panel.style.border = 'none'; panel.style.borderRadius = '5px'; panel.style.padding = '20px'; // panel.style.border = '2px solid #000'; panel.style.zIndex = '9999'; const categories = ['italic', 'quotationmarks', 'plaintext', 'custom', 'charbubble', 'userbubble']; const colorPickers = {}; // Set a fixed width for the labels const labelWidth = '150px'; categories.forEach(category => { const colorPicker = document.createElement('input'); colorPicker.type = 'color'; // Retrieve stored color from local storage const storedColor = localStorage.getItem(`${category}_color`); if (storedColor) { colorPicker.value = storedColor; } colorPickers[category] = colorPicker; // Create a div to hold color picker const colorDiv = document.createElement('div'); colorDiv.style.position = 'relative'; colorDiv.style.width = '20px'; colorDiv.style.height = '20px'; colorDiv.style.marginLeft = '10px'; colorDiv.style.top = '5px'; colorDiv.style.backgroundColor = colorPicker.value; colorDiv.style.display = 'inline-block'; colorDiv.style.marginRight = '10px'; colorDiv.style.cursor = 'pointer'; colorDiv.style.border = '1px solid black'; // Event listener to open color picker when the color square is clicked colorDiv.addEventListener('click', function () { colorPicker.click(); }); // Event listener to update the color div when the color changes colorPicker.addEventListener('input', function () { colorDiv.style.backgroundColor = colorPicker.value; }); const label = document.createElement('label'); label.style.width = labelWidth; // Set fixed width for the label label.appendChild(document.createTextNode(`${category}: `)); // Reset button for each color picker const resetButton = createButton('↺', function () { colorPicker.value = getDefaultColor(category); colorDiv.style.backgroundColor = colorPicker.value; }); resetButton.style.position = 'relative'; resetButton.style.top = '1px'; // Create a div to hold label, color picker, and reset button const containerDiv = document.createElement('div'); containerDiv.appendChild(label); containerDiv.appendChild(colorDiv); containerDiv.appendChild(resetButton); panel.appendChild(containerDiv); panel.appendChild(document.createElement('br')); }); // Custom word list input const wordListInput = document.createElement('input'); wordListInput.type = 'text'; wordListInput.placeholder = 'Separate words with commas'; wordListInput.style.width = '250px'; wordListInput.style.height = '35px'; wordListInput.style.borderRadius = '3px'; wordListInput.style.marginBottom = '10px'; panel.appendChild(wordListInput); panel.appendChild(document.createElement('br')); const wordListContainer = document.createElement('div'); wordListContainer.style.display = 'flex'; wordListContainer.style.flexWrap = 'wrap'; wordListContainer.style.maxWidth = '300px'; // Set a fixed maximum width for the container // Display custom word list buttons const wordListArray = JSON.parse(localStorage.getItem('wordlist_cc')) || []; const wordListButtons = []; function createWordButton(word) { const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); const removeSymbol = isMobile ? '×' : '🞮'; const wordButton = createButton(`${word} ${removeSymbol}`, function() { // Remove the word from the list and update the panel const index = wordListArray.indexOf(word); if (index !== -1) { wordListArray.splice(index, 1); updateWordListButtons(); } }); // Word Buttons wordButton.style.borderRadius = '3px'; wordButton.style.border = 'none'; if (currentTheme === 'dark') { wordButton.style.backgroundColor = '#26272B'; } else { wordButton.style.backgroundColor = '#E4E4E7'; } wordButton.style.marginBottom = '5px'; wordButton.style.marginRight = '5px'; wordButton.style.fontSize = '16px'; wordButton.classList.add('word-button'); return wordButton; } function updateWordListButtons() { wordListContainer.innerHTML = ''; // Clear the container wordListArray.forEach(word => { const wordButton = createWordButton(word); wordListContainer.appendChild(wordButton); }); } // Append wordListContainer to the panel updateWordListButtons(); // Add Words button const addWordsButton = document.createElement('button'); addWordsButton.textContent = 'Add'; addWordsButton.style.marginTop = '-8px'; addWordsButton.style.marginLeft = '5px'; addWordsButton.style.borderRadius = '3px'; addWordsButton.style.border = 'none'; if (currentTheme === 'dark') { addWordsButton.style.backgroundColor = '#26272B'; } else { addWordsButton.style.backgroundColor = '#E4E4E7'; } addWordsButton.addEventListener('click', function() { // Get the input value, split into words, and add to wordListArray const wordListValue = wordListInput.value; const newWords = wordListValue.split(',').map(word => word.trim().toLowerCase()).filter(word => word !== ''); // Convert to lowercase and remove empty entries wordListArray.push(...newWords); // Update the word list buttons in the panel updateWordListButtons(); }); // Create a div to group the input and button on the same line const inputButtonContainer = document.createElement('div'); inputButtonContainer.style.display = 'flex'; inputButtonContainer.style.alignItems = 'center'; inputButtonContainer.appendChild(wordListInput); inputButtonContainer.appendChild(addWordsButton); // Append the container to the panel panel.appendChild(inputButtonContainer); panel.appendChild(wordListContainer); // Create initial word list buttons updateWordListButtons(); // OK button const okButton = document.createElement('button'); okButton.textContent = 'Confirm'; okButton.style.marginTop = '-20px'; okButton.style.width = '75px'; okButton.style.height = '35px'; okButton.style.marginRight = '5px'; okButton.style.borderRadius = '3px'; okButton.style.border = 'none'; if (currentTheme === 'dark') { okButton.style.backgroundColor = '#26272B'; } else { okButton.style.backgroundColor = '#D9D9DF'; } okButton.style.position = 'relative'; okButton.style.left = '24%'; //okButton.style.transform = 'translateX(-50%)'; okButton.addEventListener('click', function() { // Save selected colors to local storage categories.forEach(category => { const oldValue = localStorage.getItem(`${category}_color`); const newValue = colorPickers[category].value; if (oldValue !== newValue) { localStorage.setItem(`${category}_color`, newValue); // If 'plaintext' color is changed, auto-reload the page if (category === 'plaintext') { window.location.reload(); } } }); // Save custom word list to local storage const wordListValue = wordListInput.value; const newWords = wordListValue.split(',').map(word => word.trim().toLowerCase()).filter(word => word !== ''); // Convert to lowercase and remove empty entries const uniqueNewWords = Array.from(new Set(newWords)); // Remove duplicates // Check for existing words and add only new ones uniqueNewWords.forEach(newWord => { if (!wordListArray.includes(newWord)) { wordListArray.push(newWord); } }); localStorage.setItem('wordlist_cc', JSON.stringify(wordListArray)); updateWordListButtons(); // Close the panel panel.remove(); }); // Cancel button const cancelButton = document.createElement('button'); cancelButton.textContent = 'Cancel'; cancelButton.style.marginTop = '-20px'; cancelButton.style.borderRadius = '3px'; cancelButton.style.width = '75px'; cancelButton.style.marginLeft = '5px'; cancelButton.style.height = '35px'; cancelButton.style.border = 'none'; if (currentTheme === 'dark') { cancelButton.style.backgroundColor = '#5E5E5E'; } else { cancelButton.style.backgroundColor = '#CBD2D4'; } cancelButton.style.position = 'relative'; cancelButton.style.left = '25%'; cancelButton.addEventListener('click', function() { // Close the panel without saving panel.remove(); }); panel.appendChild(document.createElement('br')); panel.appendChild(okButton); panel.appendChild(cancelButton); document.body.appendChild(panel); } // Function to get the default color for a category function getDefaultColor(category) { const currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light'; if (currentTheme === 'dark') { const defaultColors = { 'italic': '#E0DF7F', 'quotationmarks': '#FFFFFF', 'plaintext': '#A2A2AC', 'custom': '#E0DF7F', 'charbubble': '#26272B', 'userbubble': '#303136' }; return defaultColors[category]; } else { const defaultColors = { 'italic': '#4F7AA6', 'quotationmarks': '#000000', 'plaintext': '#374151', 'custom': '#4F7AA6', 'charbubble': '#E4E4E7', 'userbubble': '#D9D9DF' }; return defaultColors[category]; } } const mainButton = createButton('', function() { const colorPanelExists = document.getElementById('colorPanel'); if (!colorPanelExists) { createColorPanel(); } }); // Set the background image of the button to the provided image mainButton.style.backgroundImage = "url('https://i.imgur.com/yBgJ3za.png')"; mainButton.style.backgroundSize = "cover"; mainButton.style.position = "fixed"; // Use "fixed" for a position relative to the viewport mainButton.style.top = "10px"; // Adjust the top position as needed mainButton.style.right = "10px"; // Adjust the right position as needed mainButton.style.width = "22px"; // Adjust the width and height as needed mainButton.style.height = "22px"; // Adjust the width and height as needed // Function to insert the mainButton into the body of the document function insertMainButton() { document.body.appendChild(mainButton); } // Call the function to insert the mainButton into the body insertMainButton(); console.info('c.ai Text Color Button appended to the top right corner.');