c.ai X Text Color

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

当前为 2024-04-05 提交的版本,查看 最新版本

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