Last active
November 28, 2025 05:53
-
-
Save kiranwayne/76e1415d5c0ac38fa66e8043ba5b2e69 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Venice Enhanced | |
| // @namespace http://tampermonkey.net/ | |
| // @version 0.7.1 | |
| // @description Customize chat width, justification, set custom model selector height, add collapsable/movable Table of Contents (Questions only) with top offset on venice.ai. Handles message deletion & edits, and SPA navigation. Aligns scroll to top with offset. Show/hide via menu. Handles Shadow DOM. Optimized observer. Better ToC text extraction. | |
| // @author kiranwayne | |
| // @match https://venice.ai/* | |
| // @updateURL https://gist.github.com/kiranwayne/76e1415d5c0ac38fa66e8043ba5b2e69/raw/venice_enhanced.js | |
| // @downloadURL https://gist.github.com/kiranwayne/76e1415d5c0ac38fa66e8043ba5b2e69/raw/venice_enhanced.js | |
| // @grant GM_getValue | |
| // @grant GM_setValue | |
| // @grant GM_registerMenuCommand | |
| // @grant GM_unregisterMenuCommand | |
| // @run-at document-end | |
| // ==/UserScript== | |
| (async () => { | |
| 'use strict'; | |
| // --- Configuration & Constants --- | |
| const SCRIPT_NAME = 'Venice Enhanced'; | |
| const SCRIPT_VERSION = '0.7.1'; // Consistent "Use Default" Logic | |
| const SCRIPT_AUTHOR = 'kiranwayne'; | |
| const CONFIG_PREFIX = 'veniceEnhancedControls_v3_'; // Prefix for GM_setValue keys | |
| const MAX_WIDTH_PX_KEY = CONFIG_PREFIX + 'maxWidthPx'; | |
| const USE_DEFAULT_WIDTH_KEY = CONFIG_PREFIX + 'useDefaultWidth'; | |
| const JUSTIFY_KEY = CONFIG_PREFIX + 'justifyEnabled'; | |
| // Model Selector Config Keys | |
| const USE_DEFAULT_MODEL_HEIGHT_KEY = CONFIG_PREFIX + 'useDefaultModelHeight'; // Renamed | |
| const MODEL_HEIGHT_PX_KEY = CONFIG_PREFIX + 'modelHeightPx'; | |
| const UI_VISIBLE_KEY = CONFIG_PREFIX + 'uiVisible'; | |
| const TOC_VISIBLE_KEY = CONFIG_PREFIX + 'tocVisible'; | |
| const TOC_COLLAPSED_KEY = CONFIG_PREFIX + 'tocCollapsed'; | |
| const TOC_POS_TOP_KEY = CONFIG_PREFIX + 'tocPosTop'; | |
| const TOC_POS_LEFT_KEY = CONFIG_PREFIX + 'tocPosLeft'; | |
| const WIDTH_STYLE_ID = 'vm-venice-width-style'; | |
| const JUSTIFY_STYLE_ID = 'vm-venice-justify-style'; | |
| const MODEL_SELECTOR_STYLE_ID = 'vm-venice-model-selector-style'; | |
| const GLOBAL_STYLE_ID = 'vm-venice-global-style'; | |
| const SCROLL_MARGIN_STYLE_ID = 'vm-venice-scroll-margin-style'; | |
| const SETTINGS_PANEL_ID = 'venice-userscript-settings-panel'; | |
| const TOC_PANEL_ID = 'venice-userscript-toc-panel'; | |
| const TOC_LIST_ID = 'venice-userscript-toc-list'; | |
| const TOC_TOGGLE_BTN_ID = 'venice-userscript-toc-toggle-btn'; | |
| const TOC_HEADER_CLASS = 'venice-toc-header'; | |
| // --- SELECTORS --- | |
| const CHAT_MESSAGES_AREA_SELECTOR = 'div.chat-messages-content'; | |
| // Selectors for specific content blocks | |
| const USER_CONTENT_SELECTOR = 'div[data-testid="user-message"]'; | |
| const AI_CONTENT_SELECTOR = 'div[data-testid="text-chat-message"]'; | |
| // Top level row containers | |
| const USER_MESSAGE_BLOCK_SELECTOR = `${CHAT_MESSAGES_AREA_SELECTOR} > div:has(${USER_CONTENT_SELECTOR})`; | |
| const AI_MESSAGE_BLOCK_SELECTOR = `${CHAT_MESSAGES_AREA_SELECTOR} > div:has(${AI_CONTENT_SELECTOR})`; | |
| // For TOC extraction | |
| const USER_MESSAGE_CONTENT_COMPONENT_SELECTOR = USER_CONTENT_SELECTOR; | |
| const CONFIRM_EDIT_BUTTON_SELECTOR = 'button[aria-label="Confirm edits"]'; | |
| const EDIT_MESSAGE_INPUT_SELECTOR = 'div[aria-label="editable markdown"], textarea[placeholder*="Edit message"]'; | |
| const CHAT_CONTAINER_SELECTOR_FOR_OBSERVER = CHAT_MESSAGES_AREA_SELECTOR; | |
| // This selector targets the outermost message containers for sizing and centering. | |
| const WIDTH_TARGET_SELECTOR = `${USER_MESSAGE_BLOCK_SELECTOR}, ${AI_MESSAGE_BLOCK_SELECTOR}`; | |
| const JUSTIFY_TARGET_SELECTOR = CHAT_MESSAGES_AREA_SELECTOR; | |
| const SCROLL_MARGIN_TARGET_SELECTOR = USER_MESSAGE_BLOCK_SELECTOR; | |
| // Dimensions | |
| const SCRIPT_DEFAULT_WIDTH_PX = 1000; | |
| const MIN_WIDTH_PX = 500; | |
| const MAX_WIDTH_PX = 2000; | |
| const STEP_WIDTH_PX = 10; | |
| const DEFAULT_MODEL_HEIGHT_PX = 600; | |
| const MIN_MODEL_HEIGHT_PX = 300; | |
| const MAX_MODEL_HEIGHT_PX = 1200; | |
| const STEP_MODEL_HEIGHT_PX = 10; | |
| const TOC_MAX_TEXT_LENGTH = 60; | |
| const TOC_DEBOUNCE_DELAY_MS = 500; | |
| const URL_CHANGE_RESCAN_DELAY_MS = 300; | |
| const TOC_DEFAULT_TOP_PX = 50; | |
| const SCROLL_TARGET_TOP_MARGIN_PX = 70; | |
| let config = { | |
| maxWidthPx: SCRIPT_DEFAULT_WIDTH_PX, useDefaultWidth: false, | |
| justifyEnabled: true, | |
| useDefaultModelHeight: true, modelHeightPx: DEFAULT_MODEL_HEIGHT_PX, | |
| uiVisible: false, tocVisible: true, tocCollapsed: false, | |
| tocPosTop: TOC_DEFAULT_TOP_PX + 'px', tocPosLeft: '' | |
| }; | |
| // UI Refs | |
| let settingsPanel = null; | |
| let widthSlider = null, widthLabel = null, widthInput = null, defaultWidthCheckbox = null; | |
| let modelHeightSlider = null, modelHeightLabel = null, modelHeightInput = null, defaultModelHeightCheckbox = null; | |
| let justifyCheckbox = null; | |
| let menuCommandId_ToggleUI = null, menuCommandId_ToggleToc = null; | |
| const allStyleRoots = new Set(); | |
| let tocPanel = null, tocList = null, tocToggleButton = null, tocHeader = null; | |
| let messageCounterForTocIds = 0; | |
| let pageObserverInstance = null; | |
| let chatContentObserverInstance = null; | |
| let currentChatContainerElement = null; | |
| let lastKnownHref = window.location.href; | |
| let isDragging = false, initialMouseX = 0, initialMouseY = 0, initialPanelX = 0, initialPanelY = 0; | |
| // --- Helper Functions --- | |
| function debounce(func, wait) { | |
| let timeout; | |
| return function executedFunction(...args) { | |
| const later = () => { clearTimeout(timeout); func(...args); }; | |
| clearTimeout(timeout); timeout = setTimeout(later, wait); | |
| }; | |
| } | |
| async function loadSettings() { | |
| // Width | |
| config.maxWidthPx = await GM_getValue(MAX_WIDTH_PX_KEY, SCRIPT_DEFAULT_WIDTH_PX); | |
| config.maxWidthPx = Math.max(MIN_WIDTH_PX, Math.min(MAX_WIDTH_PX, config.maxWidthPx)); | |
| config.useDefaultWidth = await GM_getValue(USE_DEFAULT_WIDTH_KEY, false); | |
| // Justify | |
| config.justifyEnabled = await GM_getValue(JUSTIFY_KEY, true); | |
| // Model Selector | |
| config.useDefaultModelHeight = await GM_getValue(USE_DEFAULT_MODEL_HEIGHT_KEY, true); | |
| config.modelHeightPx = await GM_getValue(MODEL_HEIGHT_PX_KEY, DEFAULT_MODEL_HEIGHT_PX); | |
| config.modelHeightPx = Math.max(MIN_MODEL_HEIGHT_PX, Math.min(MAX_MODEL_HEIGHT_PX, config.modelHeightPx)); | |
| // UI/TOC | |
| config.uiVisible = await GM_getValue(UI_VISIBLE_KEY, false); | |
| config.tocVisible = await GM_getValue(TOC_VISIBLE_KEY, false); | |
| config.tocCollapsed = await GM_getValue(TOC_COLLAPSED_KEY, false); | |
| const defaultLeftDynamic = Math.max(10, window.innerWidth - 300 - 20) + 'px'; | |
| config.tocPosTop = await GM_getValue(TOC_POS_TOP_KEY, TOC_DEFAULT_TOP_PX + 'px'); | |
| config.tocPosLeft = await GM_getValue(TOC_POS_LEFT_KEY, defaultLeftDynamic); | |
| if (typeof config.tocPosTop !== 'string' || !config.tocPosTop.endsWith('px')) { config.tocPosTop = TOC_DEFAULT_TOP_PX + 'px'; } | |
| if (typeof config.tocPosLeft !== 'string' || !config.tocPosLeft.endsWith('px')) { config.tocPosLeft = defaultLeftDynamic; } | |
| } | |
| async function saveSetting(key, value) { | |
| if (key === MAX_WIDTH_PX_KEY) { | |
| const numValue = parseInt(value, 10); | |
| if (!isNaN(numValue)) { | |
| const clampedValue = Math.max(MIN_WIDTH_PX, Math.min(MAX_WIDTH_PX, numValue)); | |
| await GM_setValue(key, clampedValue); config.maxWidthPx = clampedValue; | |
| } | |
| } else if (key === MODEL_HEIGHT_PX_KEY) { | |
| const numValue = parseInt(value, 10); | |
| if (!isNaN(numValue)) { | |
| const clampedValue = Math.max(MIN_MODEL_HEIGHT_PX, Math.min(MAX_MODEL_HEIGHT_PX, numValue)); | |
| await GM_setValue(key, clampedValue); config.modelHeightPx = clampedValue; | |
| } | |
| } else { | |
| await GM_setValue(key, value); | |
| if (key === USE_DEFAULT_WIDTH_KEY) config.useDefaultWidth = value; | |
| else if (key === JUSTIFY_KEY) config.justifyEnabled = value; | |
| else if (key === USE_DEFAULT_MODEL_HEIGHT_KEY) config.useDefaultModelHeight = value; | |
| else if (key === UI_VISIBLE_KEY) config.uiVisible = value; | |
| else if (key === TOC_VISIBLE_KEY) config.tocVisible = value; | |
| else if (key === TOC_COLLAPSED_KEY) config.tocCollapsed = value; | |
| } | |
| } | |
| async function saveTocPosition(top, left) { | |
| const topStr = typeof top === 'number' ? `${top}px` : top; | |
| const leftStr = typeof left === 'number' ? `${left}px` : left; | |
| if (topStr && topStr.endsWith('px')) { await GM_setValue(TOC_POS_TOP_KEY, topStr); config.tocPosTop = topStr; } | |
| if (leftStr && leftStr.endsWith('px')) { await GM_setValue(TOC_POS_LEFT_KEY, leftStr); config.tocPosLeft = leftStr; } | |
| } | |
| // --- Style Generation Functions --- | |
| function getWidthCss() { | |
| if (config.useDefaultWidth) return ''; | |
| const containerRule = `${WIDTH_TARGET_SELECTOR} { | |
| max-width: ${config.maxWidthPx}px !important; | |
| margin-left: auto !important; | |
| margin-right: auto !important; | |
| width: 100% !important; | |
| }`; | |
| const innerConstraintsSelector = ` | |
| ${USER_CONTENT_SELECTOR}, ${USER_CONTENT_SELECTOR} .prose, | |
| ${AI_CONTENT_SELECTOR}, ${AI_CONTENT_SELECTOR} > div, ${AI_CONTENT_SELECTOR} .prose, | |
| div:has(> ${AI_CONTENT_SELECTOR}), div:has(> div > ${AI_CONTENT_SELECTOR}) | |
| `; | |
| const innerConstraintsRule = `${innerConstraintsSelector} { | |
| max-width: none !important; | |
| width: 100% !important; | |
| }`; | |
| return containerRule + '\n' + innerConstraintsRule; | |
| } | |
| function getJustifyCss() { | |
| if (!config.justifyEnabled) return ''; | |
| return `${JUSTIFY_TARGET_SELECTOR} { text-align: justify !important; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; }`; | |
| } | |
| function getModelSelectorCss() { | |
| if (config.useDefaultModelHeight) return ''; | |
| // Forces a fixed height based on user setting | |
| return ` | |
| @media screen and (min-width: 768px) { | |
| div:has(> div > div > div[data-sentry-component="ModelList"]) { | |
| height: ${config.modelHeightPx}px !important; | |
| min-height: 0 !important; /* Allow override */ | |
| max-height: 90vh !important; | |
| } | |
| } | |
| `; | |
| } | |
| function getScrollMarginCss() { | |
| return `${SCROLL_MARGIN_TARGET_SELECTOR} { scroll-margin-top: ${SCROLL_TARGET_TOP_MARGIN_PX}px !important; }`; | |
| } | |
| function getGlobalSpinnerCss() { | |
| return ` | |
| #${SETTINGS_PANEL_ID} input[type=number] { -moz-appearance: textfield !important; } | |
| #${SETTINGS_PANEL_ID} input[type=number]::-webkit-inner-spin-button, | |
| #${SETTINGS_PANEL_ID} input[type=number]::-webkit-outer-spin-button { -webkit-appearance: inner-spin-button !important; opacity: 1 !important; cursor: pointer; } | |
| #${SETTINGS_PANEL_ID}, #${TOC_PANEL_ID} { cursor: default !important; } | |
| #${TOC_PANEL_ID} { | |
| position: fixed; z-index: 9998; background: #2a2b32; color: #ECECF1; border: 1px solid #4a4b54; | |
| border-radius: 6px; padding: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.25); | |
| max-height: 70vh; overflow: hidden; min-width: 200px; max-width: 300px; | |
| font-size: 0.9em; display: flex; flex-direction: column; | |
| user-select: none; -webkit-user-select: none; -ms-user-select: none; | |
| transition: opacity 0.3s ease-out; opacity: 1; | |
| } | |
| #${TOC_PANEL_ID}.hidden { opacity: 0; pointer-events: none; } | |
| #${TOC_PANEL_ID} .${TOC_HEADER_CLASS} { | |
| display: flex; justify-content: space-between; align-items: center; | |
| margin-bottom: 8px; padding-bottom: 5px; border-bottom: 1px solid #4a4b54; | |
| flex-shrink: 0; cursor: move; | |
| } | |
| #${TOC_PANEL_ID} h5 { margin: 0; font-size: 1em; font-weight: bold; color: #FFFFFF; border-bottom: none; padding-bottom: 0; flex-grow: 1; text-align: center; cursor: default; } | |
| #${TOC_TOGGLE_BTN_ID} { background: none; border: none; color: #b7b9cc; cursor: pointer; padding: 2px 4px; font-size: 1.2em; line-height: 1; border-radius: 3px; margin-left: 5px; } | |
| #${TOC_TOGGLE_BTN_ID}:hover { background-color: #4a4b54; color: #FFFFFF; } | |
| #${TOC_LIST_ID} { list-style: none; padding: 0; margin: 0; overflow-y: auto; flex-grow: 1; min-height: 0; user-select: text; -webkit-user-select: text; -ms-user-select: text; } | |
| #${TOC_PANEL_ID}[data-collapsed="true"] #${TOC_LIST_ID} { display: none; } | |
| #${TOC_PANEL_ID}:not([data-collapsed="true"]) #${TOC_LIST_ID} { display: block; } | |
| #${TOC_LIST_ID} li { margin-bottom: 5px; } | |
| #${TOC_LIST_ID} a { color: #b7b9cc; text-decoration: none; display: block; padding: 3px 5px; border-radius: 3px; transition: background-color 0.2s, color 0.2s; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; } | |
| #${TOC_LIST_ID} a:hover { background-color: #4a4b54; color: #FFFFFF; } | |
| #${TOC_LIST_ID} .toc-placeholder { color: #999; font-style: italic; padding: 3px 5px; } | |
| #${TOC_PANEL_ID}.dragging { opacity: 0.85; border: 1px dashed #aaa; } | |
| `; | |
| } | |
| // --- Style Injection / Update / Removal Functions --- | |
| function injectOrUpdateStyle(root, styleId, cssContent) { | |
| if (!root) return; let style = root.querySelector(`#${styleId}`); if (cssContent) { if (!style) { style = document.createElement('style'); style.id = styleId; style.textContent = cssContent; if (root === document.head || (root.nodeType === Node.ELEMENT_NODE && root.shadowRoot === null) || root.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { root.appendChild(style); } else if (root.shadowRoot) { root.shadowRoot.appendChild(style); } } else if (style.textContent !== cssContent) { style.textContent = cssContent; } } else { if (style) { style.remove(); } } | |
| } | |
| function applyGlobalHeadStyles() { | |
| if (document.head) { injectOrUpdateStyle(document.head, GLOBAL_STYLE_ID, getGlobalSpinnerCss()); } | |
| } | |
| function applyWidthStyleToAllRoots() { | |
| const widthCss = getWidthCss(); allStyleRoots.forEach(root => { if (root) injectOrUpdateStyle(root, WIDTH_STYLE_ID, widthCss); }); | |
| } | |
| function applyJustificationStyleToAllRoots() { | |
| const justifyCss = getJustifyCss(); allStyleRoots.forEach(root => { if (root) injectOrUpdateStyle(root, JUSTIFY_STYLE_ID, justifyCss); }); | |
| } | |
| function applyModelSelectorStyleToAllRoots() { | |
| const modelCss = getModelSelectorCss(); allStyleRoots.forEach(root => { if (root) injectOrUpdateStyle(root, MODEL_SELECTOR_STYLE_ID, modelCss); }); | |
| } | |
| function applyScrollMarginStyleToAllRoots() { | |
| const scrollMarginCss = getScrollMarginCss(); allStyleRoots.forEach(root => { if (root) injectOrUpdateStyle(root, SCROLL_MARGIN_STYLE_ID, scrollMarginCss); }); | |
| } | |
| // --- UI State Update --- | |
| function updateUIState() { | |
| if (settingsPanel) { | |
| // Width | |
| if(defaultWidthCheckbox) defaultWidthCheckbox.checked = config.useDefaultWidth; | |
| const isCustomWidthEnabled = !config.useDefaultWidth; | |
| if(widthSlider) widthSlider.disabled = !isCustomWidthEnabled; | |
| if(widthInput) widthInput.disabled = !isCustomWidthEnabled; | |
| if(widthLabel) widthLabel.style.opacity = isCustomWidthEnabled ? 1 : 0.5; | |
| if(widthSlider) { widthSlider.style.opacity = isCustomWidthEnabled ? 1 : 0.5; widthSlider.value = config.maxWidthPx; } | |
| if(widthInput) { widthInput.style.opacity = isCustomWidthEnabled ? 1 : 0.5; widthInput.value = config.maxWidthPx; } | |
| if(widthLabel) widthLabel.textContent = `${config.maxWidthPx}px`; | |
| // Model Height | |
| if(defaultModelHeightCheckbox) defaultModelHeightCheckbox.checked = config.useDefaultModelHeight; | |
| const isCustomModelHeightEnabled = !config.useDefaultModelHeight; | |
| if(modelHeightSlider) modelHeightSlider.disabled = !isCustomModelHeightEnabled; | |
| if(modelHeightInput) modelHeightInput.disabled = !isCustomModelHeightEnabled; | |
| if(modelHeightLabel) modelHeightLabel.style.opacity = isCustomModelHeightEnabled ? 1 : 0.5; | |
| if(modelHeightSlider) { modelHeightSlider.style.opacity = isCustomModelHeightEnabled ? 1 : 0.5; modelHeightSlider.value = config.modelHeightPx; } | |
| if(modelHeightInput) { modelHeightInput.style.opacity = isCustomModelHeightEnabled ? 1 : 0.5; modelHeightInput.value = config.modelHeightPx; } | |
| if(modelHeightLabel) modelHeightLabel.textContent = `${config.modelHeightPx}px`; | |
| // Justify | |
| if(justifyCheckbox) justifyCheckbox.checked = config.justifyEnabled; | |
| } | |
| if (tocPanel) { | |
| tocPanel.setAttribute('data-collapsed', config.tocCollapsed.toString()); | |
| if(tocToggleButton) { | |
| tocToggleButton.textContent = config.tocCollapsed ? '⊕' : '⊖'; | |
| tocToggleButton.setAttribute('aria-label', config.tocCollapsed ? 'Expand List' : 'Collapse List'); | |
| } | |
| } | |
| } | |
| // --- Click Outside Handler --- | |
| async function handleClickOutside(event) { | |
| let clickedOutsideSettings = false; let clickedOutsideToc = false; if (settingsPanel && document.body.contains(settingsPanel) && !settingsPanel.contains(event.target)) { clickedOutsideSettings = true; } if (tocPanel && document.body.contains(tocPanel) && !tocPanel.contains(event.target)) { clickedOutsideToc = true; } if (clickedOutsideSettings && (clickedOutsideToc || !tocPanel || !config.tocVisible) && config.uiVisible) { await saveSetting(UI_VISIBLE_KEY, false); removeSettingsUI(); updateTampermonkeyMenu(); updateUIState(); } | |
| } | |
| // --- Drag Handlers --- | |
| function handleMouseDown(e) { | |
| if (e.button !== 0 || e.target.closest('button')) return; if (!tocPanel) return; isDragging = true; initialMouseX = e.clientX; initialMouseY = e.clientY; const rect = tocPanel.getBoundingClientRect(); initialPanelX = rect.left; initialPanelY = rect.top; tocPanel.classList.add('dragging'); document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); e.preventDefault(); | |
| } | |
| function handleMouseMove(e) { | |
| if (!isDragging || !tocPanel) return; const deltaX = e.clientX - initialMouseX; const deltaY = e.clientY - initialMouseY; let newPanelX = initialPanelX + deltaX; let newPanelY = initialPanelY + deltaY; const panelWidth = tocPanel.offsetWidth; const panelHeight = tocPanel.offsetHeight; const winWidth = window.innerWidth; const winHeight = window.innerHeight; newPanelX = Math.max(0, Math.min(newPanelX, winWidth - panelWidth - 5)); newPanelY = Math.max(0, Math.min(newPanelY, winHeight - panelHeight - 5)); tocPanel.style.left = newPanelX + 'px'; tocPanel.style.top = newPanelY + 'px'; | |
| } | |
| function handleMouseUp(e) { | |
| if (!isDragging || !tocPanel) return; isDragging = false; tocPanel.classList.remove('dragging'); document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); saveTocPosition(tocPanel.style.top, tocPanel.style.left); | |
| } | |
| // --- UI Creation/Removal --- | |
| function removeSettingsUI() { | |
| if (document && (!tocPanel || !config.tocVisible)) { document.removeEventListener('click', handleClickOutside, true); } | |
| settingsPanel = document.getElementById(SETTINGS_PANEL_ID); | |
| if (settingsPanel) { settingsPanel.remove(); settingsPanel = null; } | |
| // Nullify all refs | |
| widthSlider = null; widthLabel = null; widthInput = null; defaultWidthCheckbox = null; | |
| modelHeightSlider = null; modelHeightLabel = null; modelHeightInput = null; defaultModelHeightCheckbox = null; | |
| justifyCheckbox = null; | |
| if (tocPanel && config.tocVisible && !document.onclick) { if (document) document.addEventListener('click', handleClickOutside, true); } | |
| } | |
| function createControlRow(labelText, sliderMin, sliderMax, sliderStep, inputValue) { | |
| const container = document.createElement('div'); | |
| container.style.display = 'flex'; | |
| container.style.alignItems = 'center'; | |
| container.style.gap = '10px'; | |
| const label = document.createElement('span'); | |
| label.style.minWidth = '50px'; | |
| label.style.fontFamily = 'monospace'; | |
| label.style.textAlign = 'right'; | |
| label.textContent = `${inputValue}px`; | |
| const slider = document.createElement('input'); | |
| slider.type = 'range'; | |
| slider.min = sliderMin; | |
| slider.max = sliderMax; | |
| slider.step = sliderStep; | |
| slider.value = inputValue; | |
| slider.style.flexGrow = '1'; | |
| slider.style.verticalAlign = 'middle'; | |
| const input = document.createElement('input'); | |
| input.type = 'number'; | |
| input.min = sliderMin; | |
| input.max = sliderMax; | |
| input.step = sliderStep; | |
| input.value = inputValue; | |
| input.style.width = '60px'; | |
| input.style.verticalAlign = 'middle'; | |
| input.style.padding = '2px 4px'; | |
| input.style.background = '#202123'; | |
| input.style.color = '#ECECF1'; | |
| input.style.border = '1px solid #565869'; | |
| input.style.borderRadius = '4px'; | |
| container.appendChild(label); | |
| container.appendChild(slider); | |
| container.appendChild(input); | |
| return { container, label, slider, input }; | |
| } | |
| function createSettingsUI() { | |
| if (document.getElementById(SETTINGS_PANEL_ID) || !config.uiVisible) return; if (!document.body) { console.warn(`[${SCRIPT_NAME}] document.body not found, cannot create Settings UI.`); return; } | |
| 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: '300px' }); | |
| // Header | |
| 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'}); | |
| headerDiv.appendChild(titleElement); headerDiv.appendChild(versionElement); | |
| settingsPanel.appendChild(headerDiv); | |
| // --- CHAT WIDTH SECTION --- | |
| const widthSection = document.createElement('div'); | |
| widthSection.style.marginTop = '10px'; | |
| const widthTitle = document.createElement('div'); widthTitle.textContent = "Chat Width"; widthTitle.style.fontWeight = "bold"; widthTitle.style.marginBottom = "5px"; widthTitle.style.fontSize = "0.9em"; widthTitle.style.color = "#aaa"; widthSection.appendChild(widthTitle); | |
| const defaultWidthDiv = document.createElement('div'); defaultWidthDiv.style.marginBottom = '5px'; | |
| defaultWidthCheckbox = document.createElement('input'); defaultWidthCheckbox.type = 'checkbox'; defaultWidthCheckbox.id = 'venice-userscript-defaultwidth-toggle'; | |
| const defaultWidthLabel = document.createElement('label'); defaultWidthLabel.htmlFor = 'venice-userscript-defaultwidth-toggle'; defaultWidthLabel.textContent = ' Use Default Width'; defaultWidthLabel.style.cursor = 'pointer'; defaultWidthLabel.style.fontSize = "0.9em"; | |
| defaultWidthDiv.appendChild(defaultWidthCheckbox); defaultWidthDiv.appendChild(defaultWidthLabel); | |
| widthSection.appendChild(defaultWidthDiv); | |
| const widthControls = createControlRow('', MIN_WIDTH_PX, MAX_WIDTH_PX, STEP_WIDTH_PX, config.maxWidthPx); | |
| widthLabel = widthControls.label; widthSlider = widthControls.slider; widthInput = widthControls.input; | |
| widthSection.appendChild(widthControls.container); | |
| settingsPanel.appendChild(widthSection); | |
| // --- MODEL HEIGHT SECTION --- | |
| const modelSection = document.createElement('div'); | |
| modelSection.style.marginTop = '15px'; modelSection.style.borderTop = '1px solid #565869'; modelSection.style.paddingTop = '10px'; | |
| const modelTitle = document.createElement('div'); modelTitle.textContent = "Model Menu Height"; modelTitle.style.fontWeight = "bold"; modelTitle.style.marginBottom = "5px"; modelTitle.style.fontSize = "0.9em"; modelTitle.style.color = "#aaa"; modelSection.appendChild(modelTitle); | |
| const modelToggleDiv = document.createElement('div'); modelToggleDiv.style.marginBottom = '5px'; | |
| defaultModelHeightCheckbox = document.createElement('input'); defaultModelHeightCheckbox.type = 'checkbox'; defaultModelHeightCheckbox.id = 'venice-userscript-modelheight-toggle'; | |
| const modelToggleLabel = document.createElement('label'); modelToggleLabel.htmlFor = 'venice-userscript-modelheight-toggle'; modelToggleLabel.textContent = ' Use Default Height'; modelToggleLabel.style.cursor = 'pointer'; modelToggleLabel.style.fontSize = "0.9em"; | |
| modelToggleDiv.appendChild(defaultModelHeightCheckbox); modelToggleDiv.appendChild(modelToggleLabel); | |
| modelSection.appendChild(modelToggleDiv); | |
| const modelControls = createControlRow('', MIN_MODEL_HEIGHT_PX, MAX_MODEL_HEIGHT_PX, STEP_MODEL_HEIGHT_PX, config.modelHeightPx); | |
| modelHeightLabel = modelControls.label; modelHeightSlider = modelControls.slider; modelHeightInput = modelControls.input; | |
| modelSection.appendChild(modelControls.container); | |
| settingsPanel.appendChild(modelSection); | |
| // --- MISC SECTION --- | |
| const miscSection = document.createElement('div'); | |
| miscSection.style.marginTop = '15px'; miscSection.style.borderTop = '1px solid #565869'; miscSection.style.paddingTop = '10px'; | |
| const justifyDiv = document.createElement('div'); | |
| justifyCheckbox = document.createElement('input'); justifyCheckbox.type = 'checkbox'; justifyCheckbox.id = 'venice-userscript-justify-toggle'; | |
| const justifyLabel = document.createElement('label'); justifyLabel.htmlFor = 'venice-userscript-justify-toggle'; justifyLabel.textContent = ' Justify Text'; justifyLabel.style.cursor = 'pointer'; justifyLabel.style.fontSize = "0.9em"; | |
| justifyDiv.appendChild(justifyCheckbox); justifyDiv.appendChild(justifyLabel); | |
| miscSection.appendChild(justifyDiv); | |
| settingsPanel.appendChild(miscSection); | |
| document.body.appendChild(settingsPanel); | |
| // Event Listeners - Width | |
| defaultWidthCheckbox.addEventListener('change', async (e) => { await saveSetting(USE_DEFAULT_WIDTH_KEY, e.target.checked); applyWidthStyleToAllRoots(); updateUIState(); }); | |
| widthSlider.addEventListener('input', (e) => { const nw = parseInt(e.target.value, 10); config.maxWidthPx = nw; widthLabel.textContent = `${nw}px`; widthInput.value = nw; if (!config.useDefaultWidth) applyWidthStyleToAllRoots(); }); | |
| widthSlider.addEventListener('change', async (e) => { if (!config.useDefaultWidth) { await saveSetting(MAX_WIDTH_PX_KEY, parseInt(e.target.value, 10)); } }); | |
| widthInput.addEventListener('input', (e) => { let nw = parseInt(e.target.value, 10); if (isNaN(nw)) return; nw = Math.max(MIN_WIDTH_PX, Math.min(MAX_WIDTH_PX, nw)); config.maxWidthPx = nw; widthLabel.textContent = `${nw}px`; widthSlider.value = nw; if (!config.useDefaultWidth) applyWidthStyleToAllRoots(); }); | |
| widthInput.addEventListener('change', async (e) => { let fw = parseInt(e.target.value, 10); if (isNaN(fw)) fw = config.maxWidthPx; fw = Math.max(MIN_WIDTH_PX, Math.min(MAX_WIDTH_PX, fw)); e.target.value = fw; widthSlider.value = fw; widthLabel.textContent = `${fw}px`; if (!config.useDefaultWidth) { await saveSetting(MAX_WIDTH_PX_KEY, fw); applyWidthStyleToAllRoots(); } }); | |
| // Event Listeners - Model Height | |
| defaultModelHeightCheckbox.addEventListener('change', async (e) => { await saveSetting(USE_DEFAULT_MODEL_HEIGHT_KEY, e.target.checked); applyModelSelectorStyleToAllRoots(); updateUIState(); }); | |
| modelHeightSlider.addEventListener('input', (e) => { const nw = parseInt(e.target.value, 10); config.modelHeightPx = nw; modelHeightLabel.textContent = `${nw}px`; modelHeightInput.value = nw; if (!config.useDefaultModelHeight) applyModelSelectorStyleToAllRoots(); }); | |
| modelHeightSlider.addEventListener('change', async (e) => { if (!config.useDefaultModelHeight) { await saveSetting(MODEL_HEIGHT_PX_KEY, parseInt(e.target.value, 10)); } }); | |
| modelHeightInput.addEventListener('input', (e) => { let nw = parseInt(e.target.value, 10); if (isNaN(nw)) return; nw = Math.max(MIN_MODEL_HEIGHT_PX, Math.min(MAX_MODEL_HEIGHT_PX, nw)); config.modelHeightPx = nw; modelHeightLabel.textContent = `${nw}px`; modelHeightSlider.value = nw; if (!config.useDefaultModelHeight) applyModelSelectorStyleToAllRoots(); }); | |
| modelHeightInput.addEventListener('change', async (e) => { let fw = parseInt(e.target.value, 10); if (isNaN(fw)) fw = config.modelHeightPx; fw = Math.max(MIN_MODEL_HEIGHT_PX, Math.min(MAX_MODEL_HEIGHT_PX, fw)); e.target.value = fw; modelHeightSlider.value = fw; modelHeightLabel.textContent = `${fw}px`; if (!config.useDefaultModelHeight) { await saveSetting(MODEL_HEIGHT_PX_KEY, fw); applyModelSelectorStyleToAllRoots(); } }); | |
| // Event Listeners - Justify | |
| justifyCheckbox.addEventListener('change', async (e) => { await saveSetting(JUSTIFY_KEY, e.target.checked); applyJustificationStyleToAllRoots(); }); | |
| updateUIState(); | |
| if (document && (!tocPanel || !config.tocVisible)) { document.addEventListener('click', handleClickOutside, true); } applyGlobalHeadStyles(); | |
| } | |
| function removeTocUI() { | |
| if (document && (!settingsPanel || !config.uiVisible)) { document.removeEventListener('click', handleClickOutside, true); } if (tocHeader) { tocHeader.removeEventListener('mousedown', handleMouseDown); tocHeader = null; } tocPanel = document.getElementById(TOC_PANEL_ID); if (tocPanel) { tocPanel.remove(); tocPanel = null; tocList = null; tocToggleButton = null; } if (settingsPanel && config.uiVisible && !document.onclick) { if (document) document.addEventListener('click', handleClickOutside, true); } | |
| } | |
| function createTocUI() { | |
| if (document.getElementById(TOC_PANEL_ID) || !config.tocVisible) return; if (!document.body) { console.warn(`[${SCRIPT_NAME}] document.body not found, cannot create ToC UI.`); return; } tocPanel = document.createElement('div'); tocPanel.id = TOC_PANEL_ID; tocPanel.style.top = config.tocPosTop; tocPanel.style.left = config.tocPosLeft; tocHeader = document.createElement('div'); tocHeader.className = TOC_HEADER_CLASS; const title = document.createElement('h5'); title.textContent = 'Table of Contents'; tocHeader.appendChild(title); tocToggleButton = document.createElement('button'); tocToggleButton.id = TOC_TOGGLE_BTN_ID; tocToggleButton.type = 'button'; tocToggleButton.addEventListener('click', async () => { const newState = !config.tocCollapsed; await saveSetting(TOC_COLLAPSED_KEY, newState); updateUIState(); }); tocHeader.appendChild(tocToggleButton); tocHeader.addEventListener('mousedown', handleMouseDown); tocPanel.appendChild(tocHeader); tocList = document.createElement('ul'); tocList.id = TOC_LIST_ID; tocPanel.appendChild(tocList); document.body.appendChild(tocPanel); scanMessagesAndBuildToc(); updateUIState(); applyGlobalHeadStyles(); if (document && (!settingsPanel || !config.uiVisible)) { document.addEventListener('click', handleClickOutside, true); } | |
| } | |
| // --- ToC Logic --- | |
| function scrollToMessage(event) { | |
| event.preventDefault(); const targetId = event.currentTarget.getAttribute('data-target-id'); const targetElement = document.getElementById(targetId); if (targetElement) { targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); targetElement.style.transition = 'background-color 0.1s ease-in-out'; targetElement.style.backgroundColor = 'rgba(255, 255, 0, 0.1)'; setTimeout(() => { if(targetElement) { targetElement.style.backgroundColor = ''; } }, 500); } else { console.warn(`[${SCRIPT_NAME}] Target element ${targetId} not found for scrolling.`); } | |
| } | |
| const scanMessagesAndBuildToc = debounce(() => { | |
| if (!tocList || !config.tocVisible) return; tocList.innerHTML = ''; messageCounterForTocIds = 0; | |
| const allMessageBlocksNodeList = document.querySelectorAll(USER_MESSAGE_BLOCK_SELECTOR); // ToC is questions only | |
| const allMessageBlocksArray = Array.from(allMessageBlocksNodeList); | |
| const sortedMessageBlocks = allMessageBlocksArray.sort((a, b) => { if (a === b) return 0; const position = a.compareDocumentPosition(b); if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1; if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1; return 0; }); | |
| let questionCount = 0; | |
| sortedMessageBlocks.forEach(messageBlockContainer => { | |
| messageCounterForTocIds++; const messageId = `ve-msg-${messageCounterForTocIds}`; | |
| if (!messageBlockContainer.id || !messageBlockContainer.id.startsWith('ve-msg-')) { messageBlockContainer.id = messageId; } | |
| const userMessageContentElement = messageBlockContainer.querySelector(USER_MESSAGE_CONTENT_COMPONENT_SELECTOR); | |
| if (userMessageContentElement) { | |
| questionCount++; let extractedText = ''; | |
| const firstP = userMessageContentElement.querySelector('p:first-of-type'); | |
| if (firstP && (firstP.textContent || '').trim()) { extractedText = (firstP.textContent || '').trim(); } | |
| if (!extractedText) { | |
| const firstListItem = userMessageContentElement.querySelector('ol > li:first-of-type, ul > li:first-of-type'); | |
| if (firstListItem) { | |
| const pInLi = firstListItem.querySelector('p'); | |
| if (pInLi && (pInLi.textContent || '').trim()) { extractedText = (pInLi.textContent || '').trim(); } | |
| else { const liClone = firstListItem.cloneNode(true); liClone.querySelectorAll('ol, ul, pre, code, img, figure, div[class*="code"], span[class*="code"], button, svg, a[href="#"]').forEach(el => el.remove()); extractedText = (liClone.textContent || '').trim().replace(/^\d+\.\s*/, ''); } | |
| } | |
| } | |
| if (!extractedText) { const contentClone = userMessageContentElement.cloneNode(true); contentClone.querySelectorAll('pre, code, img, figure, div[class*="code"], span[class*="code"], button, svg, a[href="#"]').forEach(el => el.remove()); extractedText = (contentClone.textContent || '').trim().replace(/^\d+\.\s*/, ''); } | |
| if (!extractedText) { extractedText = `(Question ${questionCount})`; } | |
| if (extractedText.length > TOC_MAX_TEXT_LENGTH) { extractedText = extractedText.substring(0, TOC_MAX_TEXT_LENGTH) + '...'; } | |
| const listItem = document.createElement('li'); const link = document.createElement('a'); link.href = `#${messageBlockContainer.id}`; link.setAttribute('data-target-id', messageBlockContainer.id); link.appendChild(document.createTextNode(extractedText)); link.addEventListener('click', scrollToMessage); listItem.appendChild(link); tocList.appendChild(listItem); | |
| } | |
| }); | |
| if (questionCount === 0 && tocList) { const noMessagesItem = document.createElement('li'); noMessagesItem.textContent = 'Empty (No user messages found)'; noMessagesItem.className = 'toc-placeholder'; tocList.appendChild(noMessagesItem); } | |
| updateUIState(); | |
| }, TOC_DEBOUNCE_DELAY_MS); | |
| // --- Tampermonkey Menu --- | |
| function updateTampermonkeyMenu() { | |
| if (menuCommandId_ToggleUI !== null && typeof GM_unregisterMenuCommand === 'function') { try { GM_unregisterMenuCommand(menuCommandId_ToggleUI); } catch (e) {} menuCommandId_ToggleUI = null; } if (menuCommandId_ToggleToc !== null && typeof GM_unregisterMenuCommand === 'function') { try { GM_unregisterMenuCommand(menuCommandId_ToggleToc); } catch (e) {} menuCommandId_ToggleToc = null; } if (typeof GM_registerMenuCommand === 'function') { const labelUI = config.uiVisible ? 'Hide Settings Panel' : 'Show Settings Panel'; menuCommandId_ToggleUI = GM_registerMenuCommand(labelUI, async () => { const newState = !config.uiVisible; await saveSetting(UI_VISIBLE_KEY, newState); if (newState) { createSettingsUI(); } else { removeSettingsUI(); } updateTampermonkeyMenu(); updateUIState(); }); const labelToc = config.tocVisible ? 'Hide Table of Contents' : 'Show Table of Contents'; menuCommandId_ToggleToc = GM_registerMenuCommand(labelToc, async () => { const newState = !config.tocVisible; await saveSetting(TOC_VISIBLE_KEY, newState); if (newState) { createTocUI(); } else { removeTocUI(); } updateTampermonkeyMenu(); }); } | |
| } | |
| // --- Shadow DOM Handling --- | |
| function getShadowRoot(element) { try { return element.shadowRoot; } catch (e) { return null; } } | |
| function processElement(element) { | |
| const shadow = getShadowRoot(element); if (shadow && shadow.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !allStyleRoots.has(shadow)) { allStyleRoots.add(shadow); injectOrUpdateStyle(shadow, WIDTH_STYLE_ID, getWidthCss()); injectOrUpdateStyle(shadow, JUSTIFY_STYLE_ID, getJustifyCss()); injectOrUpdateStyle(shadow, MODEL_SELECTOR_STYLE_ID, getModelSelectorCss()); injectOrUpdateStyle(shadow, SCROLL_MARGIN_STYLE_ID, getScrollMarginCss()); return true; } return false; | |
| } | |
| // --- Observer Setup and SPA Navigation Handling --- | |
| const chatContentObserverCallback = (mutations) => { | |
| let potentialMessageChanged = false; | |
| mutations.forEach((mutation) => { | |
| mutation.addedNodes.forEach((node) => { | |
| if (node.nodeType === Node.ELEMENT_NODE) { | |
| try { const elementsToCheck = [node, ...node.querySelectorAll('*')]; elementsToCheck.forEach(el => { processElement(el); }); } catch (e) { } | |
| if (node.matches && (node.matches(USER_MESSAGE_BLOCK_SELECTOR) || node.matches(AI_MESSAGE_BLOCK_SELECTOR))) { potentialMessageChanged = true; } | |
| else if (node.querySelector && (node.querySelector(USER_MESSAGE_BLOCK_SELECTOR) || node.querySelector(AI_MESSAGE_BLOCK_SELECTOR))) { potentialMessageChanged = true; } | |
| } | |
| }); | |
| mutation.removedNodes.forEach((node) => { | |
| if (node.nodeType === Node.ELEMENT_NODE) { | |
| if (node.matches && (node.matches(USER_MESSAGE_BLOCK_SELECTOR) || node.matches(AI_MESSAGE_BLOCK_SELECTOR))) { potentialMessageChanged = true; } | |
| else if (node.querySelector && (node.querySelector(USER_MESSAGE_BLOCK_SELECTOR) || node.querySelector(AI_MESSAGE_BLOCK_SELECTOR))) { potentialMessageChanged = true; } | |
| } | |
| }); | |
| }); | |
| if (potentialMessageChanged && config.tocVisible) { | |
| scanMessagesAndBuildToc(); | |
| } | |
| }; | |
| function ensureChatContentObserverIsActive(targetChatElement) { | |
| if (chatContentObserverInstance && currentChatContainerElement === targetChatElement) { | |
| return; | |
| } | |
| if (chatContentObserverInstance) { | |
| chatContentObserverInstance.disconnect(); | |
| } | |
| currentChatContainerElement = targetChatElement; | |
| chatContentObserverInstance = new MutationObserver(chatContentObserverCallback); | |
| chatContentObserverInstance.observe(targetChatElement, { childList: true, subtree: true }); | |
| if (config.tocVisible) { | |
| scanMessagesAndBuildToc(); | |
| } | |
| } | |
| function startPageObserver() { | |
| const bodyElement = document.body; | |
| if (!bodyElement) { | |
| setTimeout(startPageObserver, 1000); | |
| return; | |
| } | |
| pageObserverInstance = new MutationObserver((mutations) => { | |
| const chatContainerElement = document.querySelector(CHAT_CONTAINER_SELECTOR_FOR_OBSERVER); | |
| if (chatContainerElement) { | |
| if (currentChatContainerElement !== chatContainerElement) { | |
| ensureChatContentObserverIsActive(chatContainerElement); | |
| } | |
| } else { | |
| if (currentChatContainerElement) { | |
| if (chatContentObserverInstance) chatContentObserverInstance.disconnect(); | |
| chatContentObserverInstance = null; | |
| currentChatContainerElement = null; | |
| if (tocList && config.tocVisible) { | |
| tocList.innerHTML = '<li class="toc-placeholder">Chat not active or empty.</li>'; | |
| } | |
| } | |
| } | |
| }); | |
| pageObserverInstance.observe(bodyElement, { childList: true, subtree: true }); | |
| const initialChatContainer = document.querySelector(CHAT_CONTAINER_SELECTOR_FOR_OBSERVER); | |
| if (initialChatContainer) { | |
| ensureChatContentObserverIsActive(initialChatContainer); | |
| } else { | |
| if (tocList && config.tocVisible) { | |
| // tocList.innerHTML = '<li class="toc-placeholder">Waiting for chat...</li>'; | |
| } | |
| } | |
| } | |
| function handleUrlChangeForSPA() { | |
| if (window.location.href !== lastKnownHref) { | |
| lastKnownHref = window.location.href; | |
| if (config.tocVisible) { | |
| setTimeout(scanMessagesAndBuildToc, URL_CHANGE_RESCAN_DELAY_MS); | |
| } | |
| } | |
| } | |
| // --- Initialization --- | |
| console.log(`[${SCRIPT_NAME}] v${SCRIPT_VERSION} starting...`); | |
| if (document.head) allStyleRoots.add(document.head); | |
| else { const rootNode = document.documentElement || document; allStyleRoots.add(rootNode); } | |
| await loadSettings(); | |
| applyGlobalHeadStyles(); applyWidthStyleToAllRoots(); | |
| applyJustificationStyleToAllRoots(); applyModelSelectorStyleToAllRoots(); applyScrollMarginStyleToAllRoots(); | |
| try { document.querySelectorAll('*').forEach(el => { processElement(el); }); } | |
| catch (e) { console.error(`[${SCRIPT_NAME}] Error during initial Shadow DOM scan:`, e); } | |
| if (config.uiVisible) createSettingsUI(); | |
| if (config.tocVisible) createTocUI(); | |
| updateTampermonkeyMenu(); | |
| document.addEventListener('click', (event) => { | |
| const confirmButton = event.target.closest(CONFIRM_EDIT_BUTTON_SELECTOR); | |
| if (confirmButton) { setTimeout(scanMessagesAndBuildToc, 250); } | |
| }, true); | |
| document.addEventListener('keydown', (event) => { | |
| if (event.key === 'Enter' && !event.shiftKey) { | |
| const activeElement = document.activeElement; | |
| if (activeElement && activeElement.closest(EDIT_MESSAGE_INPUT_SELECTOR)) { | |
| setTimeout(scanMessagesAndBuildToc, 250); | |
| } | |
| } | |
| }, true); | |
| setTimeout(startPageObserver, 1000); | |
| const originalPushState = history.pushState; | |
| history.pushState = function(...args) { | |
| const result = originalPushState.apply(this, args); | |
| window.dispatchEvent(new Event('pushstate')); | |
| return result; | |
| }; | |
| const originalReplaceState = history.replaceState; | |
| history.replaceState = function(...args) { | |
| const result = originalReplaceState.apply(this, args); | |
| window.dispatchEvent(new Event('replacestate')); | |
| return result; | |
| }; | |
| window.addEventListener('popstate', handleUrlChangeForSPA); | |
| window.addEventListener('pushstate', handleUrlChangeForSPA); | |
| window.addEventListener('replacestate', handleUrlChangeForSPA); | |
| setTimeout(scanMessagesAndBuildToc, 1200); | |
| // console.log(`[${SCRIPT_NAME}] Initialization complete.`); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment