c.ai X Text Color

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

当前为 2024-03-19 提交的版本,查看 最新版本

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