c.ai X Text Color

Lets you change the text colors as you wish and highlight chosen words

当前为 2024-02-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name c.ai X Text Color
  3. // @namespace c.ai X Text Color
  4. // @match https://*.character.ai/*
  5. // @grant none
  6. // @license MIT
  7. // @version 2.6.1
  8. // @author Vishanka via chatGPT
  9. // @description Lets you change the text colors as you wish and highlight chosen words
  10. // @icon https://i.imgur.com/ynjBqKW.png
  11. // ==/UserScript==
  12.  
  13.  
  14. (function () {
  15. var plaintextColor = localStorage.getItem('plaintext_color');
  16.  
  17. // Default color if 'plaintext_color' is not set
  18. var defaultColor = '#A2A2AC';
  19.  
  20. // Use the retrieved color or default color
  21. var color = plaintextColor || defaultColor;
  22.  
  23. // Create the CSS style
  24. 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; }";
  25.  
  26.  
  27. var head = document.getElementsByTagName("head")[0];
  28. var style = document.createElement("style");
  29. style.setAttribute("type", "text/css");
  30. style.innerHTML = css;
  31. head.appendChild(style);
  32. })();
  33.  
  34.  
  35. function changeColors() {
  36.  
  37. const pTags = document.getElementsByTagName("p");
  38. for (let i = 0; i < pTags.length; i++) {
  39. const pTag = pTags[i];
  40. if (
  41. pTag.dataset.colorChanged === "true" ||
  42. pTag.querySelector("code") ||
  43. pTag.querySelector("img") ||
  44. pTag.querySelector("textarea") ||
  45. pTag.querySelector("button") ||
  46. pTag.querySelector("div")
  47. ) {
  48. continue;
  49. }
  50. let text = pTag.innerHTML;
  51.  
  52. const aTags = pTag.getElementsByTagName("a"); // Get all <a> tags within the <p> tag
  53.  
  54. // Remove the <a> tags temporarily
  55. for (let j = 0; j < aTags.length; j++) {
  56. const aTag = aTags[j];
  57. text = text.replace(aTag.outerHTML, "REPLACE_ME_" + j); // Use a placeholder to be able to restore the links later
  58. }
  59.  
  60. //Changes Text within Quotation Marks to white
  61. text = text.replace(/(["“”«»].*?["“”«»])/g, `<span style="color: ${localStorage.getItem('quotationmarks_color') || '#FFFFFF'}">$1</span>`);
  62. //Changes Text within Quotation Marks and a comma at the end to yellow
  63. text = text.replace(/(["“”«»][^"]*?,["“”«»])/g, '<span style="color: #E0DF7F">$1</span>');
  64. // Changes Italic Text Color
  65. text = text.replace(/<em>(.*?)<\/em>/g,`<span style="color: ${localStorage.getItem('italic_color') || '#E0DF7F'}; font-style: italic;">$1</span>`);
  66. // Changes Textcolor within Percentages to blue
  67. // text = text.replace(/([%][^"]*?[%])/g, '<span style="color: #0000FF">$1</span>');
  68.  
  69. var wordlist_cc = JSON.parse(localStorage.getItem('wordlist_cc')) || [];
  70.  
  71. // Check if the wordlist is not empty before creating the regex
  72. if (wordlist_cc.length > 0) {
  73. // Escape special characters in each word and join them with the "|" operator
  74. var wordRegex = new RegExp('\\b(' + wordlist_cc.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') + ')\\b', 'gi');
  75.  
  76. // Replace matching words in the text
  77. text = text.replace(wordRegex, `<span style="color: ${localStorage.getItem('custom_color') || '#FFFFFF'}">$1</span>`);
  78. }
  79.  
  80. // Integrate the color changes
  81. var charbubbleColor = localStorage.getItem('charbubble_color') || '#26272B';
  82. var userbubbleColor = localStorage.getItem('userbubble_color') || '#303136';
  83.  
  84. if (window.location.href.includes("character.ai/chat")) {
  85. let css = "";
  86. css += ".bg-surface-elevation-2 { background-color: " + charbubbleColor + "; }";
  87. css += ".bg-surface-elevation-3 { background-color: " + userbubbleColor + "; }";
  88.  
  89. const style = document.createElement('style');
  90. style.type = 'text/css';
  91. style.innerHTML = css;
  92. document.head.appendChild(style);
  93. } else {
  94. let defaultCss = ".bg-surface-elevation-2, .bg-surface-elevation-3 { background-color: #26272B; }";
  95. const defaultStyle = document.createElement('style');
  96. defaultStyle.type = 'text/css';
  97. defaultStyle.innerHTML = defaultCss;
  98. document.head.appendChild(defaultStyle);
  99. }
  100.  
  101.  
  102. // Restore the <a> tags
  103. for (let j = 0; j < aTags.length; j++) {
  104. const aTag = aTags[j];
  105. text = text.replace("REPLACE_ME_" + j, aTag.outerHTML);
  106. }
  107.  
  108. pTag.innerHTML = text;
  109. pTag.dataset.colorChanged = "true";
  110. }
  111.  
  112.  
  113.  
  114. console.log("Changed colors");
  115. }
  116.  
  117. const observer = new MutationObserver(changeColors);
  118. observer.observe(document, { subtree: true, childList: true });
  119.  
  120. // Function to handle the removal of the panel
  121. function handlePanelRemoval(mutationsList, observer) {
  122. for(let mutation of mutationsList) {
  123. if (mutation.type === 'childList') {
  124. mutation.removedNodes.forEach(node => {
  125. if (node.id === 'panel') { // Assuming 'panel' is the id of your panel
  126. changeColors();
  127. }
  128. });
  129. }
  130. }
  131. }
  132.  
  133. // Observer specifically for handling removal of the panel
  134. const panelObserver = new MutationObserver(handlePanelRemoval);
  135. panelObserver.observe(document.body, { childList: true, subtree: true });
  136.  
  137.  
  138.  
  139. function createButton(symbol, onClick) {
  140. const colorpalettebutton = document.createElement('button');
  141. colorpalettebutton.innerHTML = symbol;
  142. colorpalettebutton.style.position = 'relative';
  143. colorpalettebutton.style.background = 'none';
  144. colorpalettebutton.style.border = 'none';
  145. colorpalettebutton.style.fontSize = '18px';
  146. colorpalettebutton.style.top = '-5px';
  147. colorpalettebutton.style.cursor = 'pointer';
  148. colorpalettebutton.addEventListener('click', onClick);
  149. return colorpalettebutton;
  150. }
  151.  
  152. // Function to create the color selector panel
  153. function createColorPanel() {
  154. const panel = document.createElement('div');
  155. panel.id = 'colorPanel';
  156. panel.style.position = 'fixed';
  157. panel.style.top = '50%';
  158. panel.style.left = '50%';
  159. panel.style.transform = 'translate(-50%, -50%)';
  160. panel.style.backgroundColor = 'rgba(19, 19, 22, 0.95)';
  161. panel.style.border = 'none';
  162. panel.style.borderRadius = '5px';
  163. panel.style.padding = '20px';
  164. // panel.style.border = '2px solid #000';
  165. panel.style.zIndex = '9999';
  166.  
  167. const categories = ['italic', 'quotationmarks', 'plaintext', 'custom', 'charbubble', 'userbubble'];
  168.  
  169. const colorPickers = {};
  170.  
  171. // Set a fixed width for the labels
  172. const labelWidth = '150px';
  173.  
  174. categories.forEach(category => {
  175. const colorPicker = document.createElement('input');
  176. colorPicker.type = 'color';
  177.  
  178. // Retrieve stored color from local storage
  179. const storedColor = localStorage.getItem(`${category}_color`);
  180. if (storedColor) {
  181. colorPicker.value = storedColor;
  182. }
  183.  
  184. colorPickers[category] = colorPicker;
  185.  
  186. // Create a div to hold color picker
  187. const colorDiv = document.createElement('div');
  188. colorDiv.style.position = 'relative';
  189. colorDiv.style.width = '20px';
  190. colorDiv.style.height = '20px';
  191. colorDiv.style.marginLeft = '10px';
  192. colorDiv.style.top = '5px';
  193. colorDiv.style.backgroundColor = colorPicker.value;
  194. colorDiv.style.display = 'inline-block';
  195. colorDiv.style.marginRight = '10px';
  196. colorDiv.style.cursor = 'pointer';
  197. colorDiv.style.border = '1px solid black';
  198.  
  199.  
  200. // Event listener to open color picker when the color square is clicked
  201. colorDiv.addEventListener('click', function () {
  202. colorPicker.click();
  203. });
  204.  
  205. // Event listener to update the color div when the color changes
  206. colorPicker.addEventListener('input', function () {
  207. colorDiv.style.backgroundColor = colorPicker.value;
  208. });
  209.  
  210. const label = document.createElement('label');
  211. label.style.width = labelWidth; // Set fixed width for the label
  212. label.appendChild(document.createTextNode(`${category}: `));
  213.  
  214. // Reset button for each color picker
  215. const resetButton = createButton('↺', function () {
  216. colorPicker.value = getDefaultColor(category);
  217. colorDiv.style.backgroundColor = colorPicker.value;
  218. });
  219. resetButton.style.position = 'relative';
  220. resetButton.style.top = '1px';
  221. // Create a div to hold label, color picker, and reset button
  222. const containerDiv = document.createElement('div');
  223. containerDiv.appendChild(label);
  224. containerDiv.appendChild(colorDiv);
  225. containerDiv.appendChild(resetButton);
  226.  
  227. panel.appendChild(containerDiv);
  228. panel.appendChild(document.createElement('br'));
  229. });
  230.  
  231. // Custom word list input
  232. const wordListInput = document.createElement('input');
  233. wordListInput.type = 'text';
  234. wordListInput.placeholder = 'Separate words with commas';
  235. wordListInput.style.width = '250px';
  236. wordListInput.style.height = '35px';
  237. wordListInput.style.borderRadius = '3px';
  238. wordListInput.style.marginBottom = '10px';
  239. panel.appendChild(wordListInput);
  240. panel.appendChild(document.createElement('br'));
  241.  
  242. const wordListContainer = document.createElement('div');
  243. wordListContainer.style.display = 'flex';
  244. wordListContainer.style.flexWrap = 'wrap';
  245. wordListContainer.style.maxWidth = '300px'; // Set a fixed maximum width for the container
  246.  
  247. // Display custom word list buttons
  248. const wordListArray = JSON.parse(localStorage.getItem('wordlist_cc')) || [];
  249. const wordListButtons = [];
  250.  
  251. function createWordButton(word) {
  252. const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
  253.  
  254. const removeSymbol = isMobile ? '×' : '🞮';
  255.  
  256. const wordButton = createButton(`${word} ${removeSymbol}`, function() {
  257. // Remove the word from the list and update the panel
  258. const index = wordListArray.indexOf(word);
  259. if (index !== -1) {
  260. wordListArray.splice(index, 1);
  261. updateWordListButtons();
  262. }
  263. });
  264.  
  265. // Word Buttons
  266. wordButton.style.borderRadius = '3px';
  267. wordButton.style.border = 'none';
  268. wordButton.style.backgroundColor = '#26272B';
  269. wordButton.style.marginBottom = '5px';
  270. wordButton.style.marginRight = '5px';
  271. wordButton.style.fontSize = '16px';
  272. wordButton.classList.add('word-button');
  273. return wordButton;
  274. }
  275.  
  276. function updateWordListButtons() {
  277. wordListContainer.innerHTML = ''; // Clear the container
  278. wordListArray.forEach(word => {
  279. const wordButton = createWordButton(word);
  280. wordListContainer.appendChild(wordButton);
  281. });
  282. }
  283.  
  284. // Append wordListContainer to the panel
  285.  
  286.  
  287.  
  288. updateWordListButtons();
  289.  
  290. // Add Words button
  291. const addWordsButton = document.createElement('button');
  292. addWordsButton.textContent = 'Add';
  293. addWordsButton.style.marginTop = '-8px';
  294. addWordsButton.style.marginLeft = '5px';
  295. addWordsButton.style.borderRadius = '3px';
  296. addWordsButton.style.border = 'none';
  297. addWordsButton.style.backgroundColor = '#26272B';
  298. addWordsButton.addEventListener('click', function() {
  299. // Get the input value, split into words, and add to wordListArray
  300. const wordListValue = wordListInput.value;
  301. const newWords = wordListValue.split(',').map(word => word.trim().toLowerCase()).filter(word => word !== ''); // Convert to lowercase and remove empty entries
  302. wordListArray.push(...newWords);
  303.  
  304. // Update the word list buttons in the panel
  305. updateWordListButtons();
  306. });
  307.  
  308. // Create a div to group the input and button on the same line
  309. const inputButtonContainer = document.createElement('div');
  310. inputButtonContainer.style.display = 'flex';
  311. inputButtonContainer.style.alignItems = 'center';
  312.  
  313. inputButtonContainer.appendChild(wordListInput);
  314. inputButtonContainer.appendChild(addWordsButton);
  315.  
  316. // Append the container to the panel
  317. panel.appendChild(inputButtonContainer);
  318. panel.appendChild(wordListContainer);
  319. // Create initial word list buttons
  320. updateWordListButtons();
  321.  
  322.  
  323. // OK button
  324. const okButton = document.createElement('button');
  325. okButton.textContent = 'Confirm';
  326. okButton.style.marginTop = '-20px';
  327. okButton.style.width = '75px';
  328. okButton.style.height = '35px';
  329. okButton.style.marginRight = '5px';
  330. okButton.style.borderRadius = '3px';
  331. okButton.style.border = 'none';
  332. okButton.style.backgroundColor = '#26272B';
  333. okButton.style.position = 'relative';
  334. okButton.style.left = '24%';
  335. //okButton.style.transform = 'translateX(-50%)';
  336. okButton.addEventListener('click', function() {
  337. // Save selected colors to local storage
  338. categories.forEach(category => {
  339. const oldValue = localStorage.getItem(`${category}_color`);
  340. const newValue = colorPickers[category].value;
  341.  
  342. if (oldValue !== newValue) {
  343. localStorage.setItem(`${category}_color`, newValue);
  344.  
  345. // If 'plaintext' color is changed, auto-reload the page
  346. if (category === 'plaintext') {
  347. window.location.reload();
  348. }
  349. }
  350. });
  351.  
  352.  
  353. // Save custom word list to local storage
  354. const wordListValue = wordListInput.value;
  355. const newWords = wordListValue.split(',').map(word => word.trim().toLowerCase()).filter(word => word !== ''); // Convert to lowercase and remove empty entries
  356. const uniqueNewWords = Array.from(new Set(newWords)); // Remove duplicates
  357.  
  358. // Check for existing words and add only new ones
  359. uniqueNewWords.forEach(newWord => {
  360. if (!wordListArray.includes(newWord)) {
  361. wordListArray.push(newWord);
  362. }
  363. });
  364.  
  365. localStorage.setItem('wordlist_cc', JSON.stringify(wordListArray));
  366.  
  367. updateWordListButtons();
  368.  
  369. // Close the panel
  370. panel.remove();
  371. });
  372.  
  373. // Cancel button
  374. const cancelButton = document.createElement('button');
  375. cancelButton.textContent = 'Cancel';
  376. cancelButton.style.marginTop = '-20px';
  377. cancelButton.style.borderRadius = '3px';
  378. cancelButton.style.width = '75px';
  379. cancelButton.style.marginLeft = '5px';
  380. cancelButton.style.height = '35px';
  381. cancelButton.style.border = 'none';
  382. cancelButton.style.backgroundColor = '#5E5E5E';
  383. cancelButton.style.position = 'relative';
  384. cancelButton.style.left = '25%';
  385. cancelButton.addEventListener('click', function() {
  386. // Close the panel without saving
  387. panel.remove();
  388. });
  389.  
  390. panel.appendChild(document.createElement('br'));
  391. panel.appendChild(okButton);
  392. panel.appendChild(cancelButton);
  393.  
  394. document.body.appendChild(panel);
  395. }
  396.  
  397.  
  398.  
  399. // Function to get the default color for a category
  400. function getDefaultColor(category) {
  401. const defaultColors = {
  402. 'italic': '#E0DF7F',
  403. 'quotationmarks': '#FFFFFF',
  404. 'plaintext': '#A2A2AC',
  405. 'custom': '#E0DF7F',
  406. 'charbubble': '#26272B',
  407. 'userbubble': '#303136'
  408. };
  409. return defaultColors[category];
  410. }
  411.  
  412. const mainButton = createButton('', function() {
  413. const colorPanelExists = document.getElementById('colorPanel');
  414. if (!colorPanelExists) {
  415. createColorPanel();
  416. }
  417. });
  418.  
  419. // Set the background image of the button to the provided image
  420. mainButton.style.backgroundImage = "url('https://i.imgur.com/yBgJ3za.png')";
  421. mainButton.style.backgroundSize = "cover";
  422. mainButton.style.position = "fixed"; // Use "fixed" for a position relative to the viewport
  423. mainButton.style.top = "10px"; // Adjust the top position as needed
  424. mainButton.style.right = "10px"; // Adjust the right position as needed
  425. mainButton.style.width = "22px"; // Adjust the width and height as needed
  426. mainButton.style.height = "22px"; // Adjust the width and height as needed
  427.  
  428. const targetSelector = '.w-\\[325px\\]';
  429. const secondaryTargetSelector = 'div.justify-between:nth-child(1)';
  430.  
  431. // Function to insert the mainButton into the target panel
  432. function insertMainButton(targetPanel) {
  433. targetPanel.appendChild(mainButton);
  434. }
  435.  
  436. // MutationObserver for the web version
  437. const webObserver = new MutationObserver(() => {
  438. const updatedTargetPanel = document.querySelector(targetSelector);
  439. if (updatedTargetPanel) {
  440. insertMainButton(updatedTargetPanel);
  441. webObserver.disconnect();
  442. }
  443. });
  444.  
  445. // MutationObserver for phones
  446. const phoneObserver = new MutationObserver(() => {
  447. const updatedTargetPanel2 = document.querySelector(secondaryTargetSelector);
  448. if (updatedTargetPanel2) {
  449. insertMainButton(updatedTargetPanel2);
  450. phoneObserver.disconnect();
  451. }
  452. });
  453.  
  454. // Determine whether to use webObserver or phoneObserver based on the screen width
  455. if (window.innerWidth >= 1000) {
  456. webObserver.observe(document.body, { subtree: true, childList: true });
  457. } else {
  458. phoneObserver.observe(document.body, { subtree: true, childList: true });
  459. }
  460.  
  461. console.error('Target panel not found. Waiting for changes...');