uy_uy

send_area styled

Jul 22nd, 2025
6
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 8.45 KB | Source Code | 0 0
  1. // ==UserScript==
  2. // @name         Styled Editable Textarea Overlay - Robust & Visible
  3. // @namespace    http://tampermonkey.net/
  4. // @version      0.4 // Incrementing version for clarity
  5. // @description  Overlays a styled, non-editable text area over #send_textarea while keeping the original functional.
  6. // @author       Gemini
  7. // @match        *://*/*
  8. // @grant        none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12.     'use strict';
  13.  
  14.     let styledOverlay = null;
  15.     let sendTextarea = null;
  16.     let observer = null;
  17.     let updateOverlayTimeout = null;
  18.  
  19.     // Helper to debounce the updateOverlay function
  20.     function debounce(func, delay) {
  21.         return function(...args) {
  22.             clearTimeout(updateOverlayTimeout);
  23.             updateOverlayTimeout = setTimeout(() => func.apply(this, args), delay);
  24.         };
  25.     }
  26.  
  27.     // Function to apply styling based on markdown-like syntax
  28.     function applyStyling(text) {
  29.         let styledText = text
  30.             .replace(/&/g, '&')
  31.             .replace(/</g, '&lt;')
  32.             .replace(/>/g, '&gt;')
  33.             .replace(/"(.*?)"/g, '<span style="color: #e0a331;">"$1"</span>') // Dialogue text (light blue)
  34.             .replace(/\*\*(.*?)\*\*/g, '<span style="font-weight: bold;">**$1**</span>') // Bold, keep double asterisks
  35.             .replace(/\*(.*?)\*/g, '<span style="font-style: italic;">*$1*</span>') // Italics, keep asterisks
  36.             .replace(/^###\s*(.*)/gm, '<span style="color: #6c9c4b;">### $1</span>') // H3 markdown (light salmon)
  37.             .replace(/^##\s*(.*)/gm, '<span style="color: #76c240;">## $1</span>'); // H2 markdown (gold)
  38.  
  39.         return styledText;
  40.     }
  41.  
  42.     // The core function to update the overlay's position and content
  43.     const updateOverlay = debounce(function() {
  44.         sendTextarea = document.getElementById('send_textarea');
  45.  
  46.         if (!sendTextarea) {
  47.             if (styledOverlay) {
  48.                 styledOverlay.remove();
  49.                 styledOverlay = null;
  50.             }
  51.             if (observer) {
  52.                 observer.disconnect();
  53.                 observer = null;
  54.             }
  55.             return;
  56.         }
  57.  
  58.         if (!styledOverlay) {
  59.             styledOverlay = document.createElement('div');
  60.             styledOverlay.id = 'styled_textarea_overlay';
  61.             // IMPORTANT: Use !important to override conflicting styles
  62.             styledOverlay.style.cssText = `
  63.                 position: absolute !important;
  64.                 pointer-events: none !important; /* Allows clicks to pass through to the textarea */
  65.                 white-space: pre-wrap !important; /* Preserves whitespace and wraps text */
  66.                 word-wrap: break-word !important; /* Breaks long words */
  67.                 overflow: hidden !important; /* Hide overflow if text exceeds bounds */
  68.                 box-sizing: border-box !important; /* Include padding in width/height calculations */
  69.                 z-index: 35 !important; /* Max z-index to ensure it's always on top */
  70.                 color: #9B9191 !important; /* Default text color */
  71.                 background-color: transparent !important; /* Ensure no background to obscure original textarea */
  72.                 display: block !important; /* Ensure it's not hidden by default */
  73.                 visibility: visible !important; /* Ensure it's visible */
  74.                 /* Temporary debugging styles - you can remove these once it's visible */
  75.                 border: 1px solid red !important;
  76.                 background-color: rgba(0, 0, 0, 0) !important;
  77.             `;
  78.             // Append to body for more predictable absolute positioning relative to the document
  79.             document.body.appendChild(styledOverlay);
  80.             console.log('SillyTavern: styled_textarea_overlay appended to body.');
  81.         }
  82.  
  83.         // Hide the original textarea's text visually but keep it editable
  84.         // Apply this forcefully and log to confirm
  85.         sendTextarea.style.setProperty('color', 'transparent', 'important');
  86.         sendTextarea.style.setProperty('caret-color', 'white', 'important');
  87.         console.log('SillyTavern: #send_textarea color set to transparent. Current computed color:', window.getComputedStyle(sendTextarea).color);
  88.  
  89.  
  90.         // Match position and size
  91.         const rect = sendTextarea.getBoundingClientRect();
  92.         const computedStyle = window.getComputedStyle(sendTextarea);
  93.  
  94.         // Log calculated positions for debugging
  95.         console.log('SillyTavern: #send_textarea rect:', rect);
  96.         console.log('SillyTavern: window.scrollY:', window.scrollY);
  97.  
  98.         styledOverlay.style.top = `${rect.top + window.scrollY}px`;
  99.         styledOverlay.style.left = `${rect.left + window.scrollX}px`;
  100.         styledOverlay.style.width = `${rect.width}px`;
  101.         styledOverlay.style.height = `${rect.height}px`;
  102.  
  103.         // Match font styles
  104.         styledOverlay.style.fontFamily = computedStyle.fontFamily;
  105.         styledOverlay.style.fontSize = computedStyle.fontSize;
  106.         styledOverlay.style.fontWeight = computedStyle.fontWeight;
  107.         styledOverlay.style.lineHeight = computedStyle.lineHeight;
  108.         styledOverlay.style.padding = computedStyle.padding;
  109.         styledOverlay.style.border = computedStyle.border; // Original border
  110.         styledOverlay.style.borderRadius = computedStyle.borderRadius;
  111.  
  112.         // Update content with styling
  113.         styledOverlay.innerHTML = applyStyling(sendTextarea.value);
  114.  
  115.         if (!observer) {
  116.             observer = new MutationObserver((mutations) => {
  117.                 let shouldUpdate = false;
  118.                 for (const mutation of mutations) {
  119.                     // Check for changes directly on the textarea or its immediate children
  120.                     if (mutation.target === sendTextarea || (sendTextarea && sendTextarea.contains(mutation.target))) {
  121.                         shouldUpdate = true;
  122.                         break;
  123.                     }
  124.                     // Check for changes on the textarea's parent or its children (for adjacent elements)
  125.                     if (sendTextarea.parentNode && (mutation.target === sendTextarea.parentNode || sendTextarea.parentNode.contains(mutation.target))) {
  126.                         shouldUpdate = true;
  127.                         break;
  128.                     }
  129.                 }
  130.                 if (shouldUpdate) {
  131.                     updateOverlay();
  132.                 }
  133.             });
  134.  
  135.             // Observe the textarea itself for attribute, childList, and subtree changes
  136.             observer.observe(sendTextarea, { attributes: true, attributeFilter: ['style', 'class'], childList: true, subtree: true });
  137.  
  138.             // Observe the parent of the textarea for childList and subtree changes to catch siblings affecting layout
  139.             if (sendTextarea.parentNode) {
  140.                 observer.observe(sendTextarea.parentNode, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });
  141.             }
  142.         }
  143.     }, 10);
  144.  
  145.     // Initial setup logic
  146.     function initializeScript() {
  147.         sendTextarea = document.getElementById('send_textarea');
  148.         if (sendTextarea) {
  149.             console.log('SillyTavern: #send_textarea found. Initializing overlay.');
  150.             updateOverlay();
  151.             sendTextarea.addEventListener('input', updateOverlay);
  152.             sendTextarea.addEventListener('keyup', updateOverlay);
  153.             window.addEventListener('resize', updateOverlay);
  154.             // Also call updateOverlay on scroll, in case the page is scrollable and position changes
  155.             window.addEventListener('scroll', updateOverlay);
  156.         } else {
  157.             console.log('SillyTavern: #send_textarea not found yet. Setting up observer.');
  158.             const bodyObserver = new MutationObserver((mutations, obs) => {
  159.                 if (document.getElementById('send_textarea')) {
  160.                     obs.disconnect();
  161.                     initializeScript();
  162.                 }
  163.             });
  164.             bodyObserver.observe(document.body, { childList: true, subtree: true });
  165.         }
  166.     }
  167.  
  168.     // Start the initialization when the DOM is fully loaded
  169.     window.addEventListener('load', initializeScript);
  170.     // Fallback for cases where 'load' might not fire as expected or element is loaded later
  171.     let initInterval = setInterval(() => {
  172.         if (document.getElementById('send_textarea')) {
  173.             clearInterval(initInterval);
  174.             initializeScript();
  175.         }
  176.     }, 200);
  177. })();
Advertisement
Add Comment
Please, Sign In to add comment