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