Character.ai Text Highlighter and Note Taker

Advanced text highlighting and note-taking for Character.ai with single-click and un-highlighting.

  1. // ==UserScript==
  2. // @name Character.ai Text Highlighter and Note Taker
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.5
  5. // @description Advanced text highlighting and note-taking for Character.ai with single-click and un-highlighting.
  6. // @match https://character.ai/*
  7. // @grant GM_setValue
  8. // @grant GM_getValue
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. // Enhanced styling for highlights and notes
  15. const styleElement = document.createElement('style');
  16. styleElement.textContent = `
  17. .ca-highlight {
  18. background-color: yellow;
  19. color: black;
  20. cursor: pointer;
  21. transition: background-color 0.2s ease;
  22. display: inline; /* Ensure inline display for proper spacing */
  23. white-space: pre-wrap; /* Preserve whitespace */
  24. }
  25. .ca-highlight:hover {
  26. background-color: #ffff99;
  27. }
  28. .ca-note-popup {
  29. position: fixed;
  30. top: 50%;
  31. left: 50%;
  32. transform: translate(-50%, -50%);
  33. background-color: #1a1a1a;
  34. border: 2px solid #333;
  35. border-radius: 8px;
  36. padding: 20px;
  37. width: 350px;
  38. z-index: 10000;
  39. box-shadow: 0 4px 6px rgba(0,0,0,0.5);
  40. }
  41. .ca-note-input {
  42. width: 100%;
  43. margin-bottom: 10px;
  44. padding: 8px;
  45. background-color: #2a2a2a;
  46. color: white;
  47. border: 1px solid #444;
  48. border-radius: 4px;
  49. resize: vertical;
  50. min-height: 100px;
  51. }
  52. .ca-note-buttons {
  53. display: flex;
  54. justify-content: space-between;
  55. }
  56. .ca-note-save {
  57. background-color: #4CAF50;
  58. color: white;
  59. border: none;
  60. padding: 10px 15px;
  61. border-radius: 4px;
  62. cursor: pointer;
  63. margin-right: 10px;
  64. }
  65. .ca-note-cancel {
  66. background-color: #f44336;
  67. color: white;
  68. border: none;
  69. padding: 10px 15px;
  70. border-radius: 4px;
  71. cursor: pointer;
  72. }
  73. .ca-highlight-mode-indicator {
  74. position: fixed;
  75. top: 10px;
  76. right: 10px;
  77. background-color: yellow;
  78. color: black;
  79. padding: 5px 10px;
  80. border-radius: 4px;
  81. z-index: 9999;
  82. display: none;
  83. font-weight: bold;
  84. box-shadow: 0 2px 4px rgba(0,0,0,0.2);
  85. }
  86. .ca-note-button, .ca-unhighlight-button {
  87. margin-right: 10px;
  88. background-color: #555;
  89. color: white;
  90. border: none;
  91. padding: 8px 12px;
  92. border-radius: 4px;
  93. cursor: pointer;
  94. }
  95. .ca-unhighlight-button {
  96. background-color: #ff9800;
  97. }
  98. .ca-popup-menu {
  99. position: absolute;
  100. background-color: #1a1a1a;
  101. border: 1px solid #333;
  102. border-radius: 4px;
  103. padding: 5px;
  104. z-index: 10001;
  105. box-shadow: 0 2px 4px rgba(0,0,0,0.3);
  106. }
  107. `;
  108. document.head.appendChild(styleElement);
  109.  
  110. // Create highlight mode indicator
  111. const highlightModeIndicator = document.createElement('div');
  112. highlightModeIndicator.classList.add('ca-highlight-mode-indicator');
  113. highlightModeIndicator.textContent = 'Highlight Mode';
  114. document.body.appendChild(highlightModeIndicator);
  115.  
  116. // State variables
  117. let isHighlightMode = false;
  118. const STORAGE_KEY = 'caNotes';
  119.  
  120. // Utility function to get unique identifier for a word
  121. function getWordIdentifier(word) {
  122. return `highlight_${btoa(word.trim())}`;
  123. }
  124.  
  125. // Save note for a word
  126. function saveNote(word, note) {
  127. const identifier = getWordIdentifier(word);
  128. const savedNotes = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
  129. savedNotes[identifier] = note;
  130. localStorage.setItem(STORAGE_KEY, JSON.stringify(savedNotes));
  131. }
  132.  
  133. // Retrieve note for a word
  134. function getNote(word) {
  135. const identifier = getWordIdentifier(word);
  136. const savedNotes = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
  137. return savedNotes[identifier] || '';
  138. }
  139.  
  140. // Create note popup
  141. function createNotePopup(word) {
  142. const popup = document.createElement('div');
  143. popup.classList.add('ca-note-popup');
  144.  
  145. const textarea = document.createElement('textarea');
  146. textarea.classList.add('ca-note-input');
  147. textarea.placeholder = `Enter note for "${word}"`;
  148. textarea.value = getNote(word);
  149.  
  150. const buttonContainer = document.createElement('div');
  151. buttonContainer.classList.add('ca-note-buttons');
  152.  
  153. const saveButton = document.createElement('button');
  154. saveButton.textContent = 'Save';
  155. saveButton.classList.add('ca-note-save');
  156. saveButton.addEventListener('click', () => {
  157. saveNote(word, textarea.value);
  158. document.body.removeChild(popup);
  159. });
  160.  
  161. const cancelButton = document.createElement('button');
  162. cancelButton.textContent = 'Discard';
  163. cancelButton.classList.add('ca-note-cancel');
  164. cancelButton.addEventListener('click', () => {
  165. document.body.removeChild(popup);
  166. });
  167.  
  168. buttonContainer.appendChild(saveButton);
  169. buttonContainer.appendChild(cancelButton);
  170.  
  171. popup.appendChild(textarea);
  172. popup.appendChild(buttonContainer);
  173.  
  174. return popup;
  175. }
  176.  
  177. // New function to un-highlight text
  178. function unhighlightText(element) {
  179. if (element && element.classList.contains('ca-highlight')) {
  180. // Get the text content
  181. const text = element.textContent;
  182.  
  183. // Create a text node to replace the highlighted span
  184. const textNode = document.createTextNode(text);
  185.  
  186. // Replace the span with the text node
  187. element.parentNode.replaceChild(textNode, element);
  188. }
  189. }
  190.  
  191. // Handle highlighting and un-highlighting with a single click
  192. function handleHighlightClick(e) {
  193. // Only work in highlight mode
  194. if (!isHighlightMode) return;
  195.  
  196. // Check if clicked on an existing highlight
  197. if (e.target.classList && e.target.classList.contains('ca-highlight')) {
  198. // Un-highlight the text
  199. unhighlightText(e.target);
  200. return;
  201. }
  202.  
  203. // Otherwise, create a new highlight
  204. const selection = window.getSelection();
  205. const selectedText = selection.toString();
  206.  
  207. if (selectedText && selectedText.trim() !== '') {
  208. try {
  209. // Get the range
  210. const range = selection.getRangeAt(0);
  211.  
  212. // Store the original text with spaces for note-taking
  213. const originalText = selectedText;
  214.  
  215. // Create a highlight span
  216. const highlightSpan = document.createElement('span');
  217. highlightSpan.classList.add('ca-highlight');
  218.  
  219. // Use createTextNode to preserve spaces
  220. highlightSpan.appendChild(document.createTextNode(selectedText));
  221.  
  222. // Replace selection with our highlight
  223. range.deleteContents();
  224. range.insertNode(highlightSpan);
  225.  
  226. // Clear the selection
  227. selection.removeAllRanges();
  228. } catch (error) {
  229. console.error('Error highlighting text:', error);
  230. }
  231. }
  232. }
  233.  
  234. // Add right-click handling for highlights
  235. function handleHighlightRightClick(e) {
  236. // Check if right-clicked on a highlight
  237. if (e.target.classList && e.target.classList.contains('ca-highlight')) {
  238. e.preventDefault();
  239.  
  240. // Get the highlighted text
  241. const highlightedText = e.target.textContent;
  242.  
  243. // Create and display a popup menu for the note
  244. const notePopup = createNotePopup(highlightedText);
  245. document.body.appendChild(notePopup);
  246. } else {
  247. // Add note functionality when not in highlight mode
  248. if (!isHighlightMode) {
  249. const selection = window.getSelection();
  250. const selectedText = selection.toString();
  251.  
  252. if (selectedText && selectedText.trim() !== '') {
  253. e.preventDefault();
  254.  
  255. // Create and display a popup menu for the note
  256. const notePopup = createNotePopup(selectedText);
  257. document.body.appendChild(notePopup);
  258. }
  259. }
  260. }
  261. }
  262.  
  263. // Toggle highlight mode
  264. function toggleHighlightMode() {
  265. isHighlightMode = !isHighlightMode;
  266. highlightModeIndicator.style.display = isHighlightMode ? 'block' : 'none';
  267.  
  268. // Update the indicator text to show the new mode behavior
  269. highlightModeIndicator.textContent = isHighlightMode ?
  270. 'Highlight Mode (Click to highlight/unhighlight, Right-click for notes)' :
  271. 'Highlight Mode';
  272. }
  273.  
  274. // Event listeners
  275. document.addEventListener('click', (e) => {
  276. // Single click for highlighting/unhighlighting when in highlight mode
  277. handleHighlightClick(e);
  278. });
  279.  
  280. document.addEventListener('contextmenu', (e) => {
  281. // Right-click for adding notes to highlights
  282. handleHighlightRightClick(e);
  283. });
  284.  
  285. document.addEventListener('keydown', (e) => {
  286. // Toggle highlight mode with '/'
  287. if (e.key === '/') {
  288. e.preventDefault();
  289. toggleHighlightMode();
  290. }
  291. });
  292.  
  293. // Add initial message about feature
  294. console.log('Character.ai Text Highlighter Loaded:\n- Highlight Mode: "/" to toggle\n- While in highlight mode: click to highlight/unhighlight\n- Right-click on highlighted text to add/view notes\n- Right-click on selected text to add notes when not in highlight mode');
  295. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址