Advertisement
kiranwayne

Copilot Enhanced v0.4.8

Apr 30th, 2025 (edited)
35
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 26.59 KB | Source Code | 0 0
  1. // ==UserScript==
  2. // @name         Copilot Enhanced
  3. // @namespace    http://tampermonkey.net/
  4. // @version      0.4.8
  5. // @description  Customize Text and Sidebar width, toggle justification, show/hide via menu on copilot.microsoft.com. Handles Shadow DOM & Dynamic Elements. Fixes input field typing and sidebar expansion overlap.
  6. // @author       kiranwayne
  7. // @match        https://copilot.microsoft.com/*
  8. // @grant        GM_getValue
  9. // @grant        GM_setValue
  10. // @grant        GM_registerMenuCommand
  11. // @grant        GM_unregisterMenuCommand
  12. // @run-at       document-end
  13. // ==/UserScript==
  14.  
  15. (async () => {
  16.     'use strict';
  17.  
  18.     // --- Configuration & Constants ---
  19.     const SCRIPT_NAME = 'Copilot Enhanced';
  20.     const SCRIPT_VERSION = '0.4.8'; // Version bump for fix
  21.     const SCRIPT_AUTHOR = 'kiranwayne';
  22.     const CONFIG_PREFIX = 'copilotEnhancedControls_v3_';
  23.     // Text Settings
  24.     const MAX_WIDTH_PX_KEY = CONFIG_PREFIX + 'textMaxWidthPx';
  25.     const USE_DEFAULT_TEXT_WIDTH_KEY = CONFIG_PREFIX + 'useDefaultTextWidth';
  26.     const JUSTIFY_KEY = CONFIG_PREFIX + 'justifyEnabled';
  27.     // Sidebar Settings
  28.     const SIDEBAR_WIDTH_PX_KEY = CONFIG_PREFIX + 'sidebarWidthPx';
  29.     const USE_DEFAULT_SIDEBAR_WIDTH_KEY = CONFIG_PREFIX + 'useDefaultSidebarWidth';
  30.     // UI Settings
  31.     const UI_VISIBLE_KEY = CONFIG_PREFIX + 'uiVisible';
  32.     // Style IDs
  33.     const TEXT_WIDTH_STYLE_ID = 'vm-copilot-text-width-style';
  34.     const JUSTIFY_STYLE_ID = 'vm-copilot-justify-style';
  35.     const SIDEBAR_STYLE_ID = 'vm-copilot-sidebar-style';
  36.     const SETTINGS_PANEL_ID = 'copilot-userscript-settings-panel';
  37.     // Text Width Config
  38.     const SCRIPT_DEFAULT_TEXT_WIDTH_PX = 1000;
  39.     const MIN_TEXT_WIDTH_PX = 500;
  40.     const MAX_TEXT_WIDTH_PX = 2000;
  41.     const STEP_TEXT_WIDTH_PX = 10;
  42.     // Sidebar Width Config
  43.     const SCRIPT_DEFAULT_SIDEBAR_WIDTH_PX = 500; // Default for our script if no value saved
  44.     const MIN_SIDEBAR_WIDTH_PX = 250;
  45.     const MAX_SIDEBAR_WIDTH_PX = 1000;
  46.     const STEP_SIDEBAR_WIDTH_PX = 5;
  47.  
  48.     // --- Selectors (from v0.4.6) ---
  49.     const TEXT_CONTAINER_SELECTOR = '.max-w-chat';
  50.     const OUTER_SIDEBAR_SELECTOR = 'div.h-full.will-change-auto.relative';
  51.     const INNER_SIDEBAR_SELECTOR = 'div.h-screen.w-sidebar.px-1.py-2';
  52.     const MAIN_CONTENT_WRAPPER_SELECTOR = 'main.relative.w-full.min-w-0.will-change-auto';
  53.  
  54.  
  55.     // --- State Variables ---
  56.     let config = {};
  57.     let settingsPanel = null;
  58.     let textWidthSlider = null, textWidthLabel = null, textWidthInput = null, defaultTextWidthCheckbox = null, justifyCheckbox = null;
  59.     let sidebarWidthSlider = null, sidebarWidthLabel = null, sidebarWidthInput = null, defaultSidebarWidthCheckbox = null;
  60.     let menuCommandId_ToggleUI = null;
  61.     const allStyleRoots = new Set();
  62.     let sidebarObserver = null;
  63.     let outerSidebarElement = null;
  64.     let isObserving = false;
  65.  
  66.     // --- Helper Functions --- (loadSettings, saveSetting - same as before)
  67.     async function loadSettings() {
  68.         config.textMaxWidthPx = await GM_getValue(MAX_WIDTH_PX_KEY, SCRIPT_DEFAULT_TEXT_WIDTH_PX);
  69.         config.textMaxWidthPx = Math.max(MIN_TEXT_WIDTH_PX, Math.min(MAX_TEXT_WIDTH_PX, config.textMaxWidthPx));
  70.         config.useDefaultTextWidth = await GM_getValue(USE_DEFAULT_TEXT_WIDTH_KEY, false);
  71.         config.justifyEnabled = await GM_getValue(JUSTIFY_KEY, false);
  72.         config.sidebarWidthPx = await GM_getValue(SIDEBAR_WIDTH_PX_KEY, SCRIPT_DEFAULT_SIDEBAR_WIDTH_PX);
  73.         config.sidebarWidthPx = Math.max(MIN_SIDEBAR_WIDTH_PX, Math.min(MAX_SIDEBAR_WIDTH_PX, config.sidebarWidthPx));
  74.         config.useDefaultSidebarWidth = await GM_getValue(USE_DEFAULT_SIDEBAR_WIDTH_KEY, true);
  75.         config.uiVisible = await GM_getValue(UI_VISIBLE_KEY, false);
  76.     }
  77.      async function saveSetting(key, value) {
  78.          let valueToSave = value;
  79.          if (key === MAX_WIDTH_PX_KEY) { const numValue = parseInt(value, 10); valueToSave = isNaN(numValue) ? config.textMaxWidthPx : Math.max(MIN_TEXT_WIDTH_PX, Math.min(MAX_TEXT_WIDTH_PX, numValue)); }
  80.          else if (key === SIDEBAR_WIDTH_PX_KEY) { const numValue = parseInt(value, 10); valueToSave = isNaN(numValue) ? config.sidebarWidthPx : Math.max(MIN_SIDEBAR_WIDTH_PX, Math.min(MAX_SIDEBAR_WIDTH_PX, numValue)); }
  81.          await GM_setValue(key, valueToSave);
  82.          if (key === MAX_WIDTH_PX_KEY) config.textMaxWidthPx = valueToSave; else if (key === USE_DEFAULT_TEXT_WIDTH_KEY) config.useDefaultTextWidth = valueToSave; else if (key === JUSTIFY_KEY) config.justifyEnabled = valueToSave; else if (key === SIDEBAR_WIDTH_PX_KEY) config.sidebarWidthPx = valueToSave; else if (key === USE_DEFAULT_SIDEBAR_WIDTH_KEY) config.useDefaultSidebarWidth = valueToSave; else if (key === UI_VISIBLE_KEY) config.uiVisible = valueToSave;
  83.      }
  84.  
  85.     // --- Style Generation Functions ---
  86.     function getTextWidthCss() {
  87.         return config.useDefaultTextWidth ? '' : `${TEXT_CONTAINER_SELECTOR} { max-width: ${config.textMaxWidthPx}px !important; }`;
  88.     }
  89.     function getJustifyCss() {
  90.         return config.justifyEnabled ? `${TEXT_CONTAINER_SELECTOR} { text-align: justify !important; }` : '';
  91.     }
  92.  
  93.     function getSidebarCss() { // CORRECTED for v0.4.8
  94.         if (config.useDefaultSidebarWidth) return '';
  95.  
  96.         const sidebarWidthVar = `${config.sidebarWidthPx}px`;
  97.  
  98.         return `
  99.             /* 1. Set the width of the inner sidebar content area */
  100.             ${INNER_SIDEBAR_SELECTOR} {
  101.                 width: ${sidebarWidthVar} !important;
  102.                 max-width: none !important;
  103.             }
  104.  
  105.             /* 2. Main content wrapper's primary offset (e.g., margin-left) is assumed
  106.                   to be handled by the site's own layout engine or JS in response to
  107.                   the OUTER_SIDEBAR_SELECTOR's width change (which our script sets via JS).
  108.                   Therefore, we no longer set margin-left on MAIN_CONTENT_WRAPPER_SELECTOR here.
  109.             */
  110.  
  111.             /* 3. Reposition the ::before and ::after pseudo-elements of the main content wrapper.
  112.                   Their default 'left' or 'inset-inline-start' is based on the site's
  113.                   original/default sidebar width, so we must override it.
  114.             */
  115.             ${MAIN_CONTENT_WRAPPER_SELECTOR}::before,
  116.             ${MAIN_CONTENT_WRAPPER_SELECTOR}::after {
  117.                 left: ${sidebarWidthVar} !important;
  118.                 inset-inline-start: ${sidebarWidthVar} !important;
  119.                 /* pointer-events: none !important; */ /* Uncomment if they interfere with clicks after repositioning */
  120.             }
  121.         `;
  122.     }
  123.  
  124.  
  125.     // --- Style Injection / Update / Removal --- (Same as v0.4.7)
  126.     function injectOrUpdateStyle(root, styleId, cssContent) {
  127.         if (!root) return;
  128.         const insertionPoint = (root === document.head || (root.nodeType === Node.ELEMENT_NODE && root.shadowRoot === null)) ?
  129.                                 document.head :
  130.                                 (root.nodeType === Node.DOCUMENT_FRAGMENT_NODE ? root : document.documentElement);
  131.  
  132.         if (!insertionPoint && root !== document.head) {
  133.             if (root.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {} else { return; }
  134.         }
  135.         let style = insertionPoint.querySelector(`#${styleId}`);
  136.         if (cssContent) {
  137.             if (!style) { style = document.createElement('style'); style.id = styleId; style.textContent = cssContent; insertionPoint.appendChild(style); }
  138.             else if (style.textContent !== cssContent) { style.textContent = cssContent; }
  139.         } else {
  140.             if (style) style.remove();
  141.         }
  142.     }
  143.  
  144.     // --- Sidebar Observer Management --- (Same as v0.4.7)
  145.     const sidebarObserverConfig = { attributes: true, attributeFilter: ['style'], subtree: false };
  146.     const sidebarObserverCallback = function(mutationsList, obs) {
  147.         if (!outerSidebarElement || config.useDefaultSidebarWidth) { stopSidebarObserver(); return; }
  148.         let siteModifiedStyle = false;
  149.         for (const mutation of mutationsList) { if (mutation.type === 'attributes' && mutation.attributeName === 'style' && mutation.target === outerSidebarElement) { siteModifiedStyle = true; break; } }
  150.         if (siteModifiedStyle) { const currentInlineWidth = outerSidebarElement.style.width; if (currentInlineWidth !== '0px') { forceSidebarWidth(); } }
  151.     };
  152.     function startSidebarObserver() {
  153.         if (!outerSidebarElement || config.useDefaultSidebarWidth || isObserving) { if (config.useDefaultSidebarWidth && isObserving) stopSidebarObserver(); return; }
  154.         if (!sidebarObserver) { sidebarObserver = new MutationObserver(sidebarObserverCallback); }
  155.         try { sidebarObserver.takeRecords(); sidebarObserver.observe(outerSidebarElement, sidebarObserverConfig); isObserving = true; }
  156.         catch (e) { console.error('[Copilot Enhanced] Error starting MutationObserver:', e); isObserving = false; }
  157.     }
  158.     function stopSidebarObserver() {
  159.         if (sidebarObserver && isObserving) { try { sidebarObserver.disconnect(); isObserving = false; } catch (e) { console.error('[Copilot Enhanced] Error stopping MutationObserver:', e); } }
  160.     }
  161.  
  162.     // --- Outer Sidebar Width Forcing --- (Same as v0.4.7)
  163.     function forceSidebarWidth() {
  164.         if (!outerSidebarElement) { return; }
  165.         if (config.useDefaultSidebarWidth) { if (outerSidebarElement.style.getPropertyPriority('width') === 'important') { outerSidebarElement.style.removeProperty('width'); } if (outerSidebarElement.style.getPropertyPriority('max-width') === 'important') { outerSidebarElement.style.removeProperty('max-width'); } }
  166.         else { const widthToForce = `${config.sidebarWidthPx}px`; const currentInlineWidth = outerSidebarElement.style.width; if (currentInlineWidth === '0px') { return; } const currentForcedWidth = outerSidebarElement.style.getPropertyValue('width'); const currentForcedPriority = outerSidebarElement.style.getPropertyPriority('width'); if (currentForcedWidth !== widthToForce || currentForcedPriority !== 'important') { outerSidebarElement.style.setProperty('width', widthToForce, 'important'); outerSidebarElement.style.setProperty('max-width', widthToForce, 'important'); } }
  167.     }
  168.  
  169.     // --- Global Style Application & Forcing --- (Same as v0.4.7)
  170.     function applyStylesAndForceWidth() {
  171.         const textWidthCss = getTextWidthCss(); const justifyCss = getJustifyCss(); const sidebarCssRules = getSidebarCss();
  172.         allStyleRoots.forEach(root => { if (root) { injectOrUpdateStyle(root, TEXT_WIDTH_STYLE_ID, textWidthCss); injectOrUpdateStyle(root, JUSTIFY_STYLE_ID, justifyCss); injectOrUpdateStyle(root, SIDEBAR_STYLE_ID, sidebarCssRules); } });
  173.         forceSidebarWidth(); // This sets the width of OUTER_SIDEBAR_SELECTOR
  174.         if (config.useDefaultSidebarWidth) { stopSidebarObserver(); } else { startSidebarObserver(); }
  175.     }
  176.  
  177.      // --- UI State Update --- (Same as v0.4.7)
  178.      function updateUIState() {
  179.          if (!settingsPanel) return;
  180.          if (defaultTextWidthCheckbox) defaultTextWidthCheckbox.checked = config.useDefaultTextWidth;
  181.          const isCustomTextWidthEnabled = !config.useDefaultTextWidth;
  182.          if (textWidthSlider) textWidthSlider.disabled = !isCustomTextWidthEnabled; if (textWidthInput) textWidthInput.disabled = !isCustomTextWidthEnabled; if (textWidthLabel) textWidthLabel.style.opacity = isCustomTextWidthEnabled ? 1 : 0.5; if (textWidthSlider) textWidthSlider.style.opacity = isCustomTextWidthEnabled ? 1 : 0.5; if (textWidthInput) textWidthInput.style.opacity = isCustomTextWidthEnabled ? 1 : 0.5; if (textWidthSlider) textWidthSlider.value = config.textMaxWidthPx; if (textWidthLabel) textWidthLabel.textContent = `${config.textMaxWidthPx}px`; if (justifyCheckbox) justifyCheckbox.checked = config.justifyEnabled;
  183.          if (defaultSidebarWidthCheckbox) defaultSidebarWidthCheckbox.checked = config.useDefaultSidebarWidth;
  184.          const isCustomSidebarWidthEnabled = !config.useDefaultSidebarWidth;
  185.          if (sidebarWidthSlider) sidebarWidthSlider.disabled = !isCustomSidebarWidthEnabled; if (sidebarWidthInput) sidebarWidthInput.disabled = !isCustomSidebarWidthEnabled; if (sidebarWidthLabel) sidebarWidthLabel.style.opacity = isCustomSidebarWidthEnabled ? 1 : 0.5; if (sidebarWidthSlider) sidebarWidthSlider.style.opacity = isCustomSidebarWidthEnabled ? 1 : 0.5; if (sidebarWidthInput) sidebarWidthInput.style.opacity = isCustomSidebarWidthEnabled ? 1 : 0.5; if (sidebarWidthSlider) sidebarWidthSlider.value = config.sidebarWidthPx; if (sidebarWidthLabel) sidebarWidthLabel.textContent = `${config.sidebarWidthPx}px`;
  186.      }
  187.  
  188.     // --- Click Outside Handler --- (Same as v0.4.7)
  189.     async function handleClickOutside(event) { if (settingsPanel && document.body.contains(settingsPanel) && !settingsPanel.contains(event.target)) { await saveSetting(UI_VISIBLE_KEY, false); removeSettingsUI(); updateTampermonkeyMenu(); } }
  190.  
  191.     // --- UI Creation/Removal --- (Same structure as v0.4.7)
  192.     function removeSettingsUI() { document.removeEventListener('click', handleClickOutside, true); settingsPanel = document.getElementById(SETTINGS_PANEL_ID); if (settingsPanel) { settingsPanel.remove(); settingsPanel = null; textWidthSlider = textWidthLabel = textWidthInput = defaultTextWidthCheckbox = justifyCheckbox = null; sidebarWidthSlider = sidebarWidthLabel = sidebarWidthInput = defaultSidebarWidthCheckbox = null; } }
  193.     function createSettingsUI() {
  194.         if (document.getElementById(SETTINGS_PANEL_ID) || !config.uiVisible) return; removeSettingsUI(); settingsPanel = document.createElement('div'); settingsPanel.id = SETTINGS_PANEL_ID; Object.assign(settingsPanel.style, { position: 'fixed', top: '10px', right: '10px', zIndex: '9999', display: 'block', background: '#343541', color: '#ECECF1', border: '1px solid #565869', borderRadius: '6px', padding: '15px', boxShadow: '0 4px 10px rgba(0,0,0,0.3)', minWidth: '280px' });
  195.         const headerDiv = document.createElement('div'); headerDiv.style.marginBottom = '10px'; headerDiv.style.paddingBottom = '10px'; headerDiv.style.borderBottom = '1px solid #565869'; const titleElement = document.createElement('h4'); titleElement.textContent = SCRIPT_NAME; Object.assign(titleElement.style, { margin: '0 0 5px 0', fontSize: '1.1em', fontWeight: 'bold', color: '#FFFFFF'}); const versionElement = document.createElement('p'); versionElement.textContent = `Version: ${SCRIPT_VERSION}`; Object.assign(versionElement.style, { margin: '0 0 2px 0', fontSize: '0.85em', opacity: '0.8'}); const authorElement = document.createElement('p'); authorElement.textContent = `Author: ${SCRIPT_AUTHOR}`; Object.assign(authorElement.style, { margin: '0', fontSize: '0.85em', opacity: '0.8'}); headerDiv.appendChild(titleElement); headerDiv.appendChild(versionElement); headerDiv.appendChild(authorElement); settingsPanel.appendChild(headerDiv);
  196.         const textSection = document.createElement('div'); textSection.style.marginTop = '10px'; const textHeader = document.createElement('h5'); textHeader.textContent = 'Text'; Object.assign(textHeader.style, { margin: '10px 0 5px 0', fontSize: '1em', fontWeight: 'bold', color: '#FFFFFF'}); textSection.appendChild(textHeader); const defaultTextWidthDiv = document.createElement('div'); defaultTextWidthDiv.style.marginBottom = '10px'; defaultTextWidthCheckbox = document.createElement('input'); defaultTextWidthCheckbox.type = 'checkbox'; defaultTextWidthCheckbox.id = 'copilot-userscript-default-text-width-toggle'; const defaultTextWidthLabel = document.createElement('label'); defaultTextWidthLabel.htmlFor = defaultTextWidthCheckbox.id; defaultTextWidthLabel.textContent = ' Use Default Text Width'; defaultTextWidthLabel.style.cursor = 'pointer'; defaultTextWidthDiv.appendChild(defaultTextWidthCheckbox); defaultTextWidthDiv.appendChild(defaultTextWidthLabel); textSection.appendChild(defaultTextWidthDiv); const customTextWidthControlsDiv = document.createElement('div'); customTextWidthControlsDiv.style.display = 'flex'; customTextWidthControlsDiv.style.alignItems = 'center'; customTextWidthControlsDiv.style.gap = '10px'; textWidthLabel = document.createElement('span'); textWidthLabel.style.minWidth = '50px'; textWidthLabel.style.fontFamily = 'monospace'; textWidthLabel.style.textAlign = 'right'; textWidthSlider = document.createElement('input'); textWidthSlider.type = 'range'; textWidthSlider.min = MIN_TEXT_WIDTH_PX; textWidthSlider.max = MAX_TEXT_WIDTH_PX; textWidthSlider.step = STEP_TEXT_WIDTH_PX; textWidthSlider.style.flexGrow = '1'; textWidthInput = document.createElement('input'); textWidthInput.type = 'number'; textWidthInput.min = MIN_TEXT_WIDTH_PX; textWidthInput.max = MAX_TEXT_WIDTH_PX; textWidthInput.step = STEP_TEXT_WIDTH_PX; Object.assign(textWidthInput.style, { width: '60px', padding: '2px 4px', background: '#202123', color: '#ECECF1', border: '1px solid #565869', borderRadius: '4px' }); customTextWidthControlsDiv.appendChild(textWidthLabel); customTextWidthControlsDiv.appendChild(textWidthSlider); customTextWidthControlsDiv.appendChild(textWidthInput); textSection.appendChild(customTextWidthControlsDiv); const justifyDiv = document.createElement('div'); justifyDiv.style.marginTop = '10px'; justifyCheckbox = document.createElement('input'); justifyCheckbox.type = 'checkbox'; justifyCheckbox.id = 'copilot-userscript-justify-toggle'; const justifyLabel = document.createElement('label'); justifyLabel.htmlFor = justifyCheckbox.id; justifyLabel.textContent = ' Enable Text Justification'; justifyLabel.style.cursor = 'pointer'; justifyDiv.appendChild(justifyCheckbox); justifyDiv.appendChild(justifyLabel); textSection.appendChild(justifyDiv); settingsPanel.appendChild(textSection);
  197.         const sidebarSection = document.createElement('div'); sidebarSection.style.borderTop = '1px solid #565869'; sidebarSection.style.paddingTop = '15px'; sidebarSection.style.marginTop = '15px'; const sidebarHeader = document.createElement('h5'); sidebarHeader.textContent = 'Sidebar'; Object.assign(sidebarHeader.style, { margin: '0 0 5px 0', fontSize: '1em', fontWeight: 'bold', color: '#FFFFFF'}); sidebarSection.appendChild(sidebarHeader); const defaultSidebarWidthDiv = document.createElement('div'); defaultSidebarWidthDiv.style.marginBottom = '10px'; defaultSidebarWidthCheckbox = document.createElement('input'); defaultSidebarWidthCheckbox.type = 'checkbox'; defaultSidebarWidthCheckbox.id = 'copilot-userscript-default-sidebar-width-toggle'; const defaultSidebarWidthLabel = document.createElement('label'); defaultSidebarWidthLabel.htmlFor = defaultSidebarWidthCheckbox.id; defaultSidebarWidthLabel.textContent = ' Use Default Sidebar Width'; defaultSidebarWidthLabel.style.cursor = 'pointer'; defaultSidebarWidthDiv.appendChild(defaultSidebarWidthCheckbox); defaultSidebarWidthDiv.appendChild(defaultSidebarWidthLabel); sidebarSection.appendChild(defaultSidebarWidthDiv); const customSidebarWidthControlsDiv = document.createElement('div'); customSidebarWidthControlsDiv.style.display = 'flex'; customSidebarWidthControlsDiv.style.alignItems = 'center'; customSidebarWidthControlsDiv.style.gap = '10px'; sidebarWidthLabel = document.createElement('span'); sidebarWidthLabel.style.minWidth = '50px'; sidebarWidthLabel.style.fontFamily = 'monospace'; sidebarWidthLabel.style.textAlign = 'right'; sidebarWidthSlider = document.createElement('input'); sidebarWidthSlider.type = 'range'; sidebarWidthSlider.min = MIN_SIDEBAR_WIDTH_PX; sidebarWidthSlider.max = MAX_SIDEBAR_WIDTH_PX; sidebarWidthSlider.step = STEP_SIDEBAR_WIDTH_PX; sidebarWidthSlider.style.flexGrow = '1'; sidebarWidthInput = document.createElement('input'); sidebarWidthInput.type = 'number'; sidebarWidthInput.min = MIN_SIDEBAR_WIDTH_PX; sidebarWidthInput.max = MAX_SIDEBAR_WIDTH_PX; sidebarWidthInput.step = STEP_SIDEBAR_WIDTH_PX; Object.assign(sidebarWidthInput.style, { width: '60px', padding: '2px 4px', background: '#202123', color: '#ECECF1', border: '1px solid #565869', borderRadius: '4px' }); customSidebarWidthControlsDiv.appendChild(sidebarWidthLabel); customSidebarWidthControlsDiv.appendChild(sidebarWidthSlider); customSidebarWidthControlsDiv.appendChild(sidebarWidthInput); sidebarSection.appendChild(customSidebarWidthControlsDiv); settingsPanel.appendChild(sidebarSection); document.body.appendChild(settingsPanel);
  198.  
  199.         defaultTextWidthCheckbox.addEventListener('change', async (e) => { await saveSetting(USE_DEFAULT_TEXT_WIDTH_KEY, e.target.checked); applyStylesAndForceWidth(); updateUIState(); });
  200.         textWidthSlider.addEventListener('input', (e) => { config.textMaxWidthPx = parseInt(e.target.value, 10); textWidthInput.value = config.textMaxWidthPx; updateUIState(); if (!config.useDefaultTextWidth) applyStylesAndForceWidth(); });
  201.         textWidthSlider.addEventListener('change', async (e) => { if (!config.useDefaultTextWidth) await saveSetting(MAX_WIDTH_PX_KEY, parseInt(e.target.value, 10)); });
  202.         textWidthInput.addEventListener('input', (e) => { let numVal = parseInt(e.target.value, 10); if (!isNaN(numVal) || e.target.value === '' || e.target.value === '-') { let tempVal = isNaN(numVal) ? MIN_TEXT_WIDTH_PX : numVal; textWidthSlider.value = Math.max(MIN_TEXT_WIDTH_PX, Math.min(MAX_TEXT_WIDTH_PX, tempVal)); textWidthLabel.textContent = `${tempVal}px`; if (!config.useDefaultTextWidth) { config.textMaxWidthPx = Math.max(MIN_TEXT_WIDTH_PX, Math.min(MAX_TEXT_WIDTH_PX, tempVal)); applyStylesAndForceWidth(); } } });
  203.         textWidthInput.addEventListener('change', async (e) => { let finalVal = parseInt(e.target.value, 10); finalVal = isNaN(finalVal) ? SCRIPT_DEFAULT_TEXT_WIDTH_PX : Math.max(MIN_TEXT_WIDTH_PX, Math.min(MAX_TEXT_WIDTH_PX, finalVal)); config.textMaxWidthPx = finalVal; e.target.value = finalVal; await saveSetting(MAX_WIDTH_PX_KEY, finalVal); updateUIState(); if (!config.useDefaultTextWidth) { applyStylesAndForceWidth(); } });
  204.         justifyCheckbox.addEventListener('change', async (e) => { await saveSetting(JUSTIFY_KEY, e.target.checked); applyStylesAndForceWidth(); });
  205.         defaultSidebarWidthCheckbox.addEventListener('change', async (e) => { await saveSetting(USE_DEFAULT_SIDEBAR_WIDTH_KEY, e.target.checked); applyStylesAndForceWidth(); updateUIState(); });
  206.         sidebarWidthSlider.addEventListener('input', (e) => { config.sidebarWidthPx = parseInt(e.target.value, 10); sidebarWidthInput.value = config.sidebarWidthPx; updateUIState(); if (!config.useDefaultSidebarWidth) applyStylesAndForceWidth(); });
  207.         sidebarWidthSlider.addEventListener('change', async (e) => { if (!config.useDefaultSidebarWidth) await saveSetting(SIDEBAR_WIDTH_PX_KEY, parseInt(e.target.value, 10)); });
  208.         sidebarWidthInput.addEventListener('input', (e) => { let numVal = parseInt(e.target.value, 10); if (!isNaN(numVal) || e.target.value === '' || e.target.value === '-') { let tempVal = isNaN(numVal) ? MIN_SIDEBAR_WIDTH_PX : numVal; sidebarWidthSlider.value = Math.max(MIN_SIDEBAR_WIDTH_PX, Math.min(MAX_SIDEBAR_WIDTH_PX, tempVal)); sidebarWidthLabel.textContent = `${tempVal}px`; if (!config.useDefaultSidebarWidth) { config.sidebarWidthPx = Math.max(MIN_SIDEBAR_WIDTH_PX, Math.min(MAX_SIDEBAR_WIDTH_PX, tempVal)); applyStylesAndForceWidth(); } } });
  209.         sidebarWidthInput.addEventListener('change', async (e) => { let finalVal = parseInt(e.target.value, 10); finalVal = isNaN(finalVal) ? SCRIPT_DEFAULT_SIDEBAR_WIDTH_PX : Math.max(MIN_SIDEBAR_WIDTH_PX, Math.min(MAX_SIDEBAR_WIDTH_PX, finalVal)); config.sidebarWidthPx = finalVal; e.target.value = finalVal; await saveSetting(SIDEBAR_WIDTH_PX_KEY, finalVal); updateUIState(); if (!config.useDefaultSidebarWidth) { applyStylesAndForceWidth(); } });
  210.  
  211.         updateUIState(); textWidthInput.value = config.textMaxWidthPx; sidebarWidthInput.value = config.sidebarWidthPx;
  212.         document.addEventListener('click', handleClickOutside, true);
  213.     }
  214.  
  215.     // --- Tampermonkey Menu --- (Same as v0.4.7)
  216.     function updateTampermonkeyMenu() { const commandIdToUnregister = menuCommandId_ToggleUI; menuCommandId_ToggleUI = null; if (commandIdToUnregister !== null && typeof GM_unregisterMenuCommand === 'function') { try { GM_unregisterMenuCommand(commandIdToUnregister); } catch (e) { } } const label = config.uiVisible ? 'Hide Settings Panel' : 'Show Settings Panel'; if (typeof GM_registerMenuCommand === 'function') { menuCommandId_ToggleUI = GM_registerMenuCommand(label, async () => { const newState = !config.uiVisible; await saveSetting(UI_VISIBLE_KEY, newState); if (newState) createSettingsUI(); else removeSettingsUI(); updateTampermonkeyMenu(); }); } else { console.warn(`[${SCRIPT_NAME}] GM_registerMenuCommand is not available.`); } }
  217.  
  218.     // --- Shadow DOM Handling --- (Same as v0.4.7)
  219.     function getShadowRoot(element) { try { return element.shadowRoot; } catch (e) { return null; } }
  220.     function processElement(element) { const shadow = getShadowRoot(element); if (shadow && shadow.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !allStyleRoots.has(shadow)) { allStyleRoots.add(shadow); injectOrUpdateStyle(shadow, TEXT_WIDTH_STYLE_ID, getTextWidthCss()); injectOrUpdateStyle(shadow, JUSTIFY_STYLE_ID, getJustifyCss()); injectOrUpdateStyle(shadow, SIDEBAR_STYLE_ID, getSidebarCss()); return true; } return false; }
  221.  
  222.     // --- Wait For Outer Sidebar Element --- (Same as v0.4.7)
  223.     function waitForSidebarElement() {
  224.         const checkInterval = 500; const waitTimeout = 15000; let timeWaited = 0;
  225.         const intervalId = setInterval(() => {
  226.             const targetNode = document.querySelector(OUTER_SIDEBAR_SELECTOR);
  227.             if (targetNode) { clearInterval(intervalId); outerSidebarElement = targetNode; applyStylesAndForceWidth(); }
  228.             else { timeWaited += checkInterval; if (timeWaited >= waitTimeout) { clearInterval(intervalId); console.error(`[${SCRIPT_NAME}] Outer sidebar element NOT FOUND after ${waitTimeout/1000} seconds. Selector: ${OUTER_SIDEBAR_SELECTOR}.`); applyStylesAndForceWidth(); } }
  229.         }, checkInterval);
  230.     }
  231.  
  232.     // --- Initialization --- (Same as v0.4.7)
  233.     if (document.head) allStyleRoots.add(document.head); else allStyleRoots.add(document.documentElement || document);
  234.     await loadSettings();
  235.     waitForSidebarElement();
  236.     try { document.querySelectorAll('*').forEach(el => { processElement(el); }); } catch(e) { console.error(`[${SCRIPT_NAME}] Error during initial Shadow DOM scan:`, e); }
  237.     if (config.uiVisible) createSettingsUI();
  238.     updateTampermonkeyMenu();
  239.     const shadowDomObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { if (processElement(node)) { } try { node.querySelectorAll('*').forEach(el => { if (processElement(el)) { } }); } catch(e) { } } }); }); });
  240.     shadowDomObserver.observe(document.documentElement || document.body || document, { childList: true, subtree: true });
  241.     window.addEventListener('beforeunload', () => { stopSidebarObserver(); if (shadowDomObserver) shadowDomObserver.disconnect(); });
  242.  
  243. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement