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