c.ai X Text Color

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

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

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