Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Styled Editable Textarea Overlay - Robust & Visible
- // @namespace http://tampermonkey.net/
- // @version 0.4 // Incrementing version for clarity
- // @description Overlays a styled, non-editable text area over #send_textarea while keeping the original functional.
- // @author Gemini
- // @match *://*/*
- // @grant none
- // ==/UserScript==
- (function() {
- 'use strict';
- let styledOverlay = null;
- let sendTextarea = null;
- let observer = null;
- let updateOverlayTimeout = null;
- // Helper to debounce the updateOverlay function
- function debounce(func, delay) {
- return function(...args) {
- clearTimeout(updateOverlayTimeout);
- updateOverlayTimeout = setTimeout(() => func.apply(this, args), delay);
- };
- }
- // Function to apply styling based on markdown-like syntax
- function applyStyling(text) {
- let styledText = text
- .replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"(.*?)"/g, '<span style="color: #e0a331;">"$1"</span>') // Dialogue text (light blue)
- .replace(/\*\*(.*?)\*\*/g, '<span style="font-weight: bold;">**$1**</span>') // Bold, keep double asterisks
- .replace(/\*(.*?)\*/g, '<span style="font-style: italic;">*$1*</span>') // Italics, keep asterisks
- .replace(/^###\s*(.*)/gm, '<span style="color: #6c9c4b;">### $1</span>') // H3 markdown (light salmon)
- .replace(/^##\s*(.*)/gm, '<span style="color: #76c240;">## $1</span>'); // H2 markdown (gold)
- return styledText;
- }
- // The core function to update the overlay's position and content
- const updateOverlay = debounce(function() {
- sendTextarea = document.getElementById('send_textarea');
- if (!sendTextarea) {
- if (styledOverlay) {
- styledOverlay.remove();
- styledOverlay = null;
- }
- if (observer) {
- observer.disconnect();
- observer = null;
- }
- return;
- }
- if (!styledOverlay) {
- styledOverlay = document.createElement('div');
- styledOverlay.id = 'styled_textarea_overlay';
- // IMPORTANT: Use !important to override conflicting styles
- styledOverlay.style.cssText = `
- position: absolute !important;
- pointer-events: none !important; /* Allows clicks to pass through to the textarea */
- white-space: pre-wrap !important; /* Preserves whitespace and wraps text */
- word-wrap: break-word !important; /* Breaks long words */
- overflow: hidden !important; /* Hide overflow if text exceeds bounds */
- box-sizing: border-box !important; /* Include padding in width/height calculations */
- z-index: 35 !important; /* Max z-index to ensure it's always on top */
- color: #9B9191 !important; /* Default text color */
- background-color: transparent !important; /* Ensure no background to obscure original textarea */
- display: block !important; /* Ensure it's not hidden by default */
- visibility: visible !important; /* Ensure it's visible */
- /* Temporary debugging styles - you can remove these once it's visible */
- border: 1px solid red !important;
- background-color: rgba(0, 0, 0, 0) !important;
- `;
- // Append to body for more predictable absolute positioning relative to the document
- document.body.appendChild(styledOverlay);
- console.log('SillyTavern: styled_textarea_overlay appended to body.');
- }
- // Hide the original textarea's text visually but keep it editable
- // Apply this forcefully and log to confirm
- sendTextarea.style.setProperty('color', 'transparent', 'important');
- sendTextarea.style.setProperty('caret-color', 'white', 'important');
- console.log('SillyTavern: #send_textarea color set to transparent. Current computed color:', window.getComputedStyle(sendTextarea).color);
- // Match position and size
- const rect = sendTextarea.getBoundingClientRect();
- const computedStyle = window.getComputedStyle(sendTextarea);
- // Log calculated positions for debugging
- console.log('SillyTavern: #send_textarea rect:', rect);
- console.log('SillyTavern: window.scrollY:', window.scrollY);
- styledOverlay.style.top = `${rect.top + window.scrollY}px`;
- styledOverlay.style.left = `${rect.left + window.scrollX}px`;
- styledOverlay.style.width = `${rect.width}px`;
- styledOverlay.style.height = `${rect.height}px`;
- // Match font styles
- styledOverlay.style.fontFamily = computedStyle.fontFamily;
- styledOverlay.style.fontSize = computedStyle.fontSize;
- styledOverlay.style.fontWeight = computedStyle.fontWeight;
- styledOverlay.style.lineHeight = computedStyle.lineHeight;
- styledOverlay.style.padding = computedStyle.padding;
- styledOverlay.style.border = computedStyle.border; // Original border
- styledOverlay.style.borderRadius = computedStyle.borderRadius;
- // Update content with styling
- styledOverlay.innerHTML = applyStyling(sendTextarea.value);
- if (!observer) {
- observer = new MutationObserver((mutations) => {
- let shouldUpdate = false;
- for (const mutation of mutations) {
- // Check for changes directly on the textarea or its immediate children
- if (mutation.target === sendTextarea || (sendTextarea && sendTextarea.contains(mutation.target))) {
- shouldUpdate = true;
- break;
- }
- // Check for changes on the textarea's parent or its children (for adjacent elements)
- if (sendTextarea.parentNode && (mutation.target === sendTextarea.parentNode || sendTextarea.parentNode.contains(mutation.target))) {
- shouldUpdate = true;
- break;
- }
- }
- if (shouldUpdate) {
- updateOverlay();
- }
- });
- // Observe the textarea itself for attribute, childList, and subtree changes
- observer.observe(sendTextarea, { attributes: true, attributeFilter: ['style', 'class'], childList: true, subtree: true });
- // Observe the parent of the textarea for childList and subtree changes to catch siblings affecting layout
- if (sendTextarea.parentNode) {
- observer.observe(sendTextarea.parentNode, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });
- }
- }
- }, 10);
- // Initial setup logic
- function initializeScript() {
- sendTextarea = document.getElementById('send_textarea');
- if (sendTextarea) {
- console.log('SillyTavern: #send_textarea found. Initializing overlay.');
- updateOverlay();
- sendTextarea.addEventListener('input', updateOverlay);
- sendTextarea.addEventListener('keyup', updateOverlay);
- window.addEventListener('resize', updateOverlay);
- // Also call updateOverlay on scroll, in case the page is scrollable and position changes
- window.addEventListener('scroll', updateOverlay);
- } else {
- console.log('SillyTavern: #send_textarea not found yet. Setting up observer.');
- const bodyObserver = new MutationObserver((mutations, obs) => {
- if (document.getElementById('send_textarea')) {
- obs.disconnect();
- initializeScript();
- }
- });
- bodyObserver.observe(document.body, { childList: true, subtree: true });
- }
- }
- // Start the initialization when the DOM is fully loaded
- window.addEventListener('load', initializeScript);
- // Fallback for cases where 'load' might not fire as expected or element is loaded later
- let initInterval = setInterval(() => {
- if (document.getElementById('send_textarea')) {
- clearInterval(initInterval);
- initializeScript();
- }
- }, 200);
- })();
Advertisement
Add Comment
Please, Sign In to add comment