lapa / static /script.js
Vladyslav Humennyy
Fix auto scroll
e676b08
raw
history blame
7.31 kB
(function () {
console.log("Keyboard shortcuts script loaded");
// --- Autoscroll control ---
let lastSendTs = 0;
let userPinnedScroll = false;
let chatContainer = null;
let chatObserver = null;
const now = () => Date.now();
const SEND_AUTOSCROLL_WINDOW_MS = 2500;
const NEAR_BOTTOM_PX = 120;
const getChatContainer = () => {
if (chatContainer && document.body.contains(chatContainer)) return chatContainer;
// Prefer explicit elem_id container
const root = document.getElementById('chatbot') || document.querySelector('#chatbot');
// Fallbacks: any visible gr-chatbot within left pane
const candidates = [];
if (root) candidates.push(root);
candidates.push(
document.querySelector('#left-pane .gr-chatbot'),
document.querySelector('.gr-chatbot'),
document.querySelector('#left-pane [data-testid="chatbot"]')
);
for (const el of candidates) {
if (!el) continue;
// Try common inner scroll area
let container = el.querySelector('[data-testid="bot"]') || el.querySelector('[data-testid="chatbot"]') || el;
// Walk down to the element that actually scrolls
const stack = [container];
while (stack.length) {
const cur = stack.shift();
if (!cur) continue;
const style = cur instanceof Element ? getComputedStyle(cur) : null;
const canScroll = style && (style.overflowY === 'auto' || style.overflowY === 'scroll');
if (canScroll && cur.scrollHeight > cur.clientHeight + 10) {
chatContainer = cur;
break;
}
if (cur.children && cur.children.length) stack.push(...cur.children);
}
if (!chatContainer) chatContainer = container;
if (chatContainer) break;
}
return chatContainer;
};
const isNearBottom = (el) => {
if (!el) return true;
const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
return distance <= NEAR_BOTTOM_PX;
};
const scrollToBottom = (el) => {
if (!el) return;
el.scrollTo({ top: el.scrollHeight, behavior: 'auto' });
};
const attachScrollListener = () => {
const el = getChatContainer();
if (!el) return;
el.addEventListener('scroll', () => {
// If the user scrolls up away from bottom, pin the position
userPinnedScroll = !isNearBottom(el);
}, { passive: true });
};
const observeChat = () => {
const el = getChatContainer();
if (!el) return;
if (chatObserver) chatObserver.disconnect();
chatObserver = new MutationObserver(() => {
const withinSendWindow = now() - lastSendTs < SEND_AUTOSCROLL_WINDOW_MS;
const shouldScroll = withinSendWindow || (!userPinnedScroll && isNearBottom(el));
if (shouldScroll) scrollToBottom(el);
});
chatObserver.observe(el, { childList: true, subtree: true, characterData: true });
};
const send = () => {
// Try multiple selectors to find the send button
const selectors = [
'#send-btn',
'button[id="send-btn"]',
'.gr-button:contains("Send")',
'button:contains("Send")',
'#send-btn button',
'[data-testid*="send"]',
'button[variant="primary"]'
];
let btn = null;
for (let selector of selectors) {
try {
if (selector.includes(':contains')) {
// Handle :contains selector manually
const buttons = document.querySelectorAll('button');
for (let button of buttons) {
if (button.textContent.trim() === 'Send') {
btn = button;
break;
}
}
} else {
btn = document.querySelector(selector);
}
if (btn) {
console.log("Found send button with selector:", selector);
break;
}
} catch (e) {
// Skip invalid selectors
}
}
if (btn) {
console.log("Clicking send button");
lastSendTs = now();
// When sending, allow autoscroll for initial response
userPinnedScroll = false;
// Ensure observers are up
setTimeout(() => { attachScrollListener(); observeChat(); }, 0);
btn.click();
return true;
} else {
console.log("Send button not found");
// Debug: log all buttons
const allButtons = document.querySelectorAll('button');
console.log("All buttons found:", allButtons);
return false;
}
};
const setupKeyboardShortcuts = () => {
console.log("Setting up keyboard shortcuts");
// Initialize observers once UI is present
setTimeout(() => { attachScrollListener(); observeChat(); }, 50);
setTimeout(() => { attachScrollListener(); observeChat(); }, 400);
document.addEventListener('keydown', (e) => {
const isCmdEnter = (e.metaKey || e.ctrlKey) && e.key === 'Enter';
const isEnter = e.key === 'Enter' && !e.shiftKey && !e.metaKey && !e.ctrlKey;
const isInputFocused = document.activeElement &&
(document.activeElement.matches('#chat-input textarea') ||
document.activeElement.matches('.chat-input-box textarea'));
if ((isCmdEnter || (isEnter && isInputFocused)) && isInputFocused) {
console.log("Send triggered");
e.preventDefault();
e.stopPropagation();
const success = send();
if (!success) {
console.log("Failed to send, trying again in 100ms");
setTimeout(send, 100);
}
}
if (e.key === 'Escape') {
const input = document.querySelector('#chat-input textarea') || document.querySelector('.chat-input-box textarea');
if (input) {
input.focus();
console.log("Focused input field");
}
}
}, true);
console.log("Keyboard shortcuts set up");
};
// Multiple attempts to set up shortcuts
const attempts = [100, 500, 1000, 2000];
attempts.forEach(delay => {
setTimeout(() => {
console.log(`Attempting setup after ${delay}ms`);
setupKeyboardShortcuts();
}, delay);
});
// Force light theme - disable dark mode switching
const forceTheme = () => {
// Remove any dark theme classes that might be added
document.documentElement.classList.remove('dark');
document.body.classList.remove('dark');
// Set light color scheme
document.documentElement.style.colorScheme = 'light';
// Override any theme preference that might be set
if (window.matchMedia) {
const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');
// Override the matches property to always return false
Object.defineProperty(darkModeQuery, 'matches', {
value: false,
writable: false
});
}
};
// Apply theme override immediately and on any DOM changes
forceTheme();
// Watch for any changes and reapply theme override
const observer = new MutationObserver(() => {
forceTheme();
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class', 'data-theme', 'theme']
});
// Also set up immediately if DOM is ready
if (document.readyState !== 'loading') {
setupKeyboardShortcuts();
} else {
document.addEventListener('DOMContentLoaded', setupKeyboardShortcuts);
}
})();