updated some frontend stuff
This commit is contained in:
381
src/static/frontend.js
Normal file
381
src/static/frontend.js
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
// Mobile keyboard handling
|
||||||
|
let initialViewportHeight = window.innerHeight;
|
||||||
|
|
||||||
|
function adjustForKeyboard() {
|
||||||
|
const currentHeight = window.visualViewport ? window.visualViewport.height : window.innerHeight;
|
||||||
|
const diff = initialViewportHeight - currentHeight;
|
||||||
|
const messagesWrapper = document.getElementById('messagesWrapper');
|
||||||
|
|
||||||
|
if (diff > 150) { // Keyboard is likely open
|
||||||
|
keyboardOpen = true;
|
||||||
|
if (messagesWrapper) {
|
||||||
|
messagesWrapper.style.paddingBottom = '140px'; // Even more padding for mobile keyboard
|
||||||
|
// Reset scroll state and aggressively scroll to bottom
|
||||||
|
userScrolled = false;
|
||||||
|
|
||||||
|
// Multiple scroll attempts with increasing delays
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
||||||
|
}, 100);
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
||||||
|
}, 250);
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
||||||
|
}, 400);
|
||||||
|
|
||||||
|
// iOS often needs even more attempts
|
||||||
|
if (isIOS) {
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200;
|
||||||
|
}, 600);
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200;
|
||||||
|
}, 800);
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
keyboardOpen = false;
|
||||||
|
if (messagesWrapper) {
|
||||||
|
messagesWrapper.style.paddingBottom = '80px';
|
||||||
|
// Scroll to bottom when keyboard closes too
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!userScrolled) {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100;
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!userScrolled) {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100;
|
||||||
|
}
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for viewport changes (keyboard open/close)
|
||||||
|
if (window.visualViewport) {
|
||||||
|
window.visualViewport.addEventListener('resize', adjustForKeyboard);
|
||||||
|
} else {
|
||||||
|
window.addEventListener('resize', adjustForKeyboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile-optimized focus handling for textarea
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const messageInput = document.getElementById('messageInput');
|
||||||
|
if (messageInput) {
|
||||||
|
messageInput.addEventListener('focus', function() {
|
||||||
|
// Reset scroll state when focusing input
|
||||||
|
userScrolled = false;
|
||||||
|
// Multiple timeout attempts for different mobile browsers
|
||||||
|
setTimeout(adjustForKeyboard, 200);
|
||||||
|
setTimeout(adjustForKeyboard, 400);
|
||||||
|
if (isMobile) {
|
||||||
|
setTimeout(adjustForKeyboard, 600);
|
||||||
|
setTimeout(() => {
|
||||||
|
const messagesWrapper = document.getElementById('messagesWrapper');
|
||||||
|
if (messagesWrapper) {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200;
|
||||||
|
}
|
||||||
|
}, 800);
|
||||||
|
// Additional scroll attempt for stubborn mobile browsers
|
||||||
|
setTimeout(() => {
|
||||||
|
const messagesWrapper = document.getElementById('messagesWrapper');
|
||||||
|
if (messagesWrapper) {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250;
|
||||||
|
}
|
||||||
|
}, 1200);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
messageInput.addEventListener('blur', function() {
|
||||||
|
setTimeout(adjustForKeyboard, 200);
|
||||||
|
setTimeout(adjustForKeyboard, 400);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle input events for better mobile experience
|
||||||
|
messageInput.addEventListener('input', function() {
|
||||||
|
if (isMobile && !userScrolled) {
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollToBottom(false);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mobile-optimized auto-scroll functionality
|
||||||
|
let isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768;
|
||||||
|
let isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||||
|
let keyboardOpen = false;
|
||||||
|
|
||||||
|
function scrollToBottom(smooth = true) {
|
||||||
|
const messagesWrapper = document.getElementById('messagesWrapper');
|
||||||
|
if (messagesWrapper) {
|
||||||
|
// On mobile, especially iOS, direct scrollTop assignment is more reliable
|
||||||
|
if (isMobile) {
|
||||||
|
// Different scroll behavior based on keyboard state
|
||||||
|
const extraPadding = keyboardOpen ? 200 : 100;
|
||||||
|
const targetScroll = messagesWrapper.scrollHeight + extraPadding;
|
||||||
|
messagesWrapper.scrollTop = targetScroll;
|
||||||
|
|
||||||
|
// Multiple aggressive scroll attempts for mobile reliability
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + extraPadding;
|
||||||
|
}, 50);
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + extraPadding;
|
||||||
|
}, 150);
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + extraPadding;
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
// Extra attempts when just viewing (no keyboard)
|
||||||
|
if (!keyboardOpen) {
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
||||||
|
}, 500);
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
||||||
|
}, 750);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final iOS-specific attempt
|
||||||
|
if (isIOS) {
|
||||||
|
const finalPadding = keyboardOpen ? 250 : 200;
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + finalPadding;
|
||||||
|
}, keyboardOpen ? 500 : 1000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (smooth) {
|
||||||
|
messagesWrapper.scrollTo({
|
||||||
|
top: messagesWrapper.scrollHeight,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to check if user is near bottom of messages
|
||||||
|
function isNearBottom() {
|
||||||
|
const messagesWrapper = document.getElementById('messagesWrapper');
|
||||||
|
if (!messagesWrapper) return true;
|
||||||
|
|
||||||
|
// Larger threshold for mobile to account for touch scrolling momentum
|
||||||
|
const threshold = isMobile ? 150 : 100;
|
||||||
|
return messagesWrapper.scrollHeight - messagesWrapper.scrollTop - messagesWrapper.clientHeight < threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile scroll state management
|
||||||
|
let lastScrollTop = 0;
|
||||||
|
let userScrolled = false;
|
||||||
|
let scrollTimeout;
|
||||||
|
let touchStartY = 0;
|
||||||
|
let isScrolling = false;
|
||||||
|
|
||||||
|
// Detect user scroll with mobile optimizations
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const messagesWrapper = document.getElementById('messagesWrapper');
|
||||||
|
if (messagesWrapper) {
|
||||||
|
|
||||||
|
// Handle touch events for better mobile detection
|
||||||
|
messagesWrapper.addEventListener('touchstart', function(e) {
|
||||||
|
touchStartY = e.touches[0].clientY;
|
||||||
|
isScrolling = false;
|
||||||
|
clearTimeout(scrollTimeout);
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
messagesWrapper.addEventListener('touchmove', function(e) {
|
||||||
|
if (!isScrolling) {
|
||||||
|
isScrolling = true;
|
||||||
|
const touchY = e.touches[0].clientY;
|
||||||
|
const deltaY = touchStartY - touchY;
|
||||||
|
|
||||||
|
// If user is swiping up (scrolling up), mark as user scrolled
|
||||||
|
if (deltaY < -10 && !isNearBottom()) {
|
||||||
|
userScrolled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
messagesWrapper.addEventListener('touchend', function() {
|
||||||
|
// Check scroll position after touch ends with delay for momentum scrolling
|
||||||
|
setTimeout(() => {
|
||||||
|
if (isNearBottom()) {
|
||||||
|
userScrolled = false;
|
||||||
|
}
|
||||||
|
isScrolling = false;
|
||||||
|
}, 300);
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
// Regular scroll event with debouncing for mobile
|
||||||
|
messagesWrapper.addEventListener('scroll', function() {
|
||||||
|
clearTimeout(scrollTimeout);
|
||||||
|
scrollTimeout = setTimeout(() => {
|
||||||
|
const currentScrollTop = messagesWrapper.scrollTop;
|
||||||
|
const maxScroll = messagesWrapper.scrollHeight - messagesWrapper.clientHeight;
|
||||||
|
|
||||||
|
// More lenient detection for mobile
|
||||||
|
const upScrollThreshold = isMobile ? 30 : 50;
|
||||||
|
|
||||||
|
// If user scrolled up manually, don't auto-scroll for new messages
|
||||||
|
if (currentScrollTop < lastScrollTop && currentScrollTop < maxScroll - upScrollThreshold) {
|
||||||
|
userScrolled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user scrolled to near bottom, resume auto-scrolling
|
||||||
|
if (isNearBottom()) {
|
||||||
|
userScrolled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastScrollTop = currentScrollTop;
|
||||||
|
}, isMobile ? 150 : 50); // Longer debounce for mobile
|
||||||
|
}, { passive: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mobile-optimized message adding function
|
||||||
|
function addMessageWithAutoScroll(messageElement) {
|
||||||
|
const messagesContainer = document.getElementById('messagesContainer');
|
||||||
|
if (messagesContainer && messageElement) {
|
||||||
|
messagesContainer.appendChild(messageElement);
|
||||||
|
|
||||||
|
// Only auto-scroll if user hasn't manually scrolled up
|
||||||
|
if (!userScrolled) {
|
||||||
|
const messagesWrapper = document.getElementById('messagesWrapper');
|
||||||
|
if (messagesWrapper) {
|
||||||
|
// Immediate first scroll
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100;
|
||||||
|
|
||||||
|
// Progressive scroll attempts with different padding
|
||||||
|
setTimeout(() => {
|
||||||
|
const padding = keyboardOpen ? 200 : 150;
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding;
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const padding = keyboardOpen ? 200 : 150;
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding;
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
// Extra attempts when just viewing (no keyboard)
|
||||||
|
if (!keyboardOpen && isMobile) {
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200;
|
||||||
|
}, 400);
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250;
|
||||||
|
}, 600);
|
||||||
|
// Final attempt for stubborn browsers
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 300;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// iOS-specific final attempt
|
||||||
|
if (isIOS) {
|
||||||
|
const finalPadding = keyboardOpen ? 250 : 300;
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + finalPadding;
|
||||||
|
}, keyboardOpen ? 600 : 1200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force scroll with mobile optimizations
|
||||||
|
function forceScrollToBottom() {
|
||||||
|
userScrolled = false;
|
||||||
|
const messagesWrapper = document.getElementById('messagesWrapper');
|
||||||
|
if (messagesWrapper) {
|
||||||
|
// Immediate aggressive scroll
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
||||||
|
}, 100);
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200;
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
// Extra attempts for iOS
|
||||||
|
if (isIOS) {
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250;
|
||||||
|
}, 500);
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250;
|
||||||
|
}, 700);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile-optimized mutation observer
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const messagesContainer = document.getElementById('messagesContainer');
|
||||||
|
if (messagesContainer) {
|
||||||
|
// Create a MutationObserver to watch for new messages
|
||||||
|
const observer = new MutationObserver(function(mutations) {
|
||||||
|
mutations.forEach(function(mutation) {
|
||||||
|
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
||||||
|
// Check if any added nodes are message elements
|
||||||
|
const hasNewMessage = Array.from(mutation.addedNodes).some(node =>
|
||||||
|
node.nodeType === Node.ELEMENT_NODE &&
|
||||||
|
(node.classList.contains('message-group') || node.querySelector('.message-group'))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasNewMessage && !userScrolled) {
|
||||||
|
const messagesWrapper = document.getElementById('messagesWrapper');
|
||||||
|
if (messagesWrapper) {
|
||||||
|
// Immediate scroll
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const padding = keyboardOpen ? 200 : 150;
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding;
|
||||||
|
}, 100);
|
||||||
|
setTimeout(() => {
|
||||||
|
const padding = keyboardOpen ? 250 : 200;
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding;
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
// Extra scrolling when just viewing (no keyboard)
|
||||||
|
if (!keyboardOpen && isMobile) {
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250;
|
||||||
|
}, 500);
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 300;
|
||||||
|
}, 800);
|
||||||
|
// Final aggressive attempt for viewing mode
|
||||||
|
setTimeout(() => {
|
||||||
|
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 350;
|
||||||
|
}, 1200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start observing
|
||||||
|
observer.observe(messagesContainer, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Expose functions globally so your existing JavaScript can use them
|
||||||
|
window.ByteChat = window.ByteChat || {};
|
||||||
|
window.ByteChat.scrollToBottom = scrollToBottom;
|
||||||
|
window.ByteChat.forceScrollToBottom = forceScrollToBottom;
|
||||||
|
window.ByteChat.addMessageWithAutoScroll = addMessageWithAutoScroll;
|
||||||
BIN
src/static/laptop.png
Normal file
BIN
src/static/laptop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.2 MiB |
@@ -11,7 +11,7 @@
|
|||||||
<link rel="icon" type="image/png" href="{{ url_for('static', filename='favicon.png') }}">
|
<link rel="icon" type="image/png" href="{{ url_for('static', filename='favicon.png') }}">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@4.7.2/dist/socket.io.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@4.7.2/dist/socket.io.min.js"></script>
|
||||||
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||||
|
<scrpt src="{{ url_for('static', filename='frontend.js') }}"></script>
|
||||||
</head>
|
</head>
|
||||||
<body style="overflow-y: auto;">
|
<body style="overflow-y: auto;">
|
||||||
<div class="chat-container">
|
<div class="chat-container">
|
||||||
@@ -68,45 +68,93 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feature-list" style="grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr)); gap: 1rem; margin-top: 1.5rem; width: 100%;">
|
<!-- Laptop Image and Desktop App Section -->
|
||||||
<div class="feature-item" style="padding: 1.25rem;">
|
<div style="margin-top: 2rem; display: flex; flex-direction: column; align-items: center; width: 100%;">
|
||||||
<div class="feature-icon" role="img" aria-label="Security" style="font-size: clamp(1.5rem, 5vw, 2rem);">🔐</div>
|
<img class="laptopimg"
|
||||||
<div class="feature-title" style="font-size: clamp(0.95rem, 3.5vw, 1.1rem);">End-to-End Encryption</div>
|
src="{{ url_for('static', filename='laptop.png')}}"
|
||||||
<div class="feature-desc" style="font-size: clamp(0.8rem, 3vw, 0.9rem);">Messages are encrypted with AES-256 in your browser. The server never sees your messages.</div>
|
alt="ByteChat Desktop Application"
|
||||||
</div>
|
style="width: clamp(200px, 50vw, 400px); height: auto; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); transition: transform 0.3s ease, box-shadow 0.3s ease; margin-bottom: 1.5rem; display: block;">
|
||||||
<div class="feature-item" style="padding: 1.25rem;">
|
|
||||||
<div class="feature-icon" role="img" aria-label="Real-time" style="font-size: clamp(1.5rem, 5vw, 2rem);">🌐</div>
|
<!-- local testing image. uncomment if developing locally
|
||||||
<div class="feature-title" style="font-size: clamp(0.95rem, 3.5vw, 1.1rem);">Real-time Chat</div>
|
<img class="laptopimg"
|
||||||
<div class="feature-desc" style="font-size: clamp(0.8rem, 3vw, 0.9rem);">Instant messaging with WebSocket technology for immediate message delivery.</div>
|
src="../static/laptop.png"
|
||||||
</div>
|
alt="ByteChat Desktop Application"
|
||||||
<div class="feature-item" style="padding: 1.25rem;">
|
style="width: clamp(200px, 50vw, 400px); height: auto; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); transition: transform 0.3s ease, box-shadow 0.3s ease; margin-bottom: 1.5rem; display: block;">
|
||||||
<div class="feature-icon" role="img" aria-label="Anonymous" style="font-size: clamp(1.5rem, 5vw, 2rem);">🎭</div>
|
-->
|
||||||
<div class="feature-title" style="font-size: clamp(0.95rem, 3.5vw, 1.1rem);">Anonymous Users</div>
|
|
||||||
<div class="feature-desc" style="font-size: clamp(0.8rem, 3vw, 0.9rem);">Each session gets a new random UUID. No registration or personal info required.</div>
|
<!-- Desktop App Section -->
|
||||||
|
<div style="text-align: center; max-width: 600px; padding: 0 1rem;">
|
||||||
|
<h2 style="font-size: clamp(1.25rem, 5vw, 1.75rem); margin-bottom: 1rem; color: #00ff88;">Desktop Application</h2>
|
||||||
|
<p style="font-size: clamp(0.9rem, 3.5vw, 1.1rem); margin-bottom: 1.5rem; line-height: 1.6; color: #b0b0b0;">
|
||||||
|
For a better experience, consider downloading our desktop application. It offers enhanced performance
|
||||||
|
trough native Encryption (instead of Browser-Reliant Encryption). Click the button below to download the latest version.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Desktop App Download Button -->
|
||||||
|
<div style="margin-bottom: 1rem;">
|
||||||
|
<a href="https://rattatwinko.servecounterstrike.com/gitea/rattatwinko/bytechat-desktop/releases"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="btn"
|
||||||
|
style="display: inline-block; text-decoration: none; font-size: clamp(0.85rem, 3.5vw, 1rem); padding: 0.6rem 1.5rem; min-width: 150px;">
|
||||||
|
Download Desktop App
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Release Notes Placeholder -->
|
||||||
|
<!--
|
||||||
|
<div style="margin-top: 2rem; padding: 1rem; border: 1px solid #333; border-radius: 8px; background: rgba(0, 0, 0, 0.2);">
|
||||||
|
<h3 style="font-size: clamp(1rem, 4vw, 1.25rem); margin-bottom: 0.75rem; color: #ffffff;">Release Information</h3>
|
||||||
|
<div style="font-size: clamp(0.8rem, 3vw, 0.95rem); color: #888; line-height: 1.5;">
|
||||||
|
<p>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.laptopimg:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for smaller screens */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.laptopimg {
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure images are responsive and maintain aspect ratio */
|
||||||
|
.laptopimg {
|
||||||
|
max-width: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Chat Screen -->
|
<!-- Chat Screen -->
|
||||||
<div id="chatScreen" style="display: none;" class="chat-screen">
|
<div id="chatScreen" style="display: none;" class="chat-screen">
|
||||||
<div class="room-info" id="roomInfo" style="display: none; padding: 0.6rem 0.75rem; flex-wrap: wrap; gap: 0.5rem;">
|
<div class="room-info" id="roomInfo" style="display: none; padding: 0.6rem 0.75rem; flex-wrap: wrap; gap: 0.5rem;">
|
||||||
<div class="room-details" style="min-width: auto; flex: 1;">
|
<div class="room-details" style="min-width: auto; flex: 1;">
|
||||||
<span style="font-size: clamp(0.75rem, 3vw, 0.85rem);">Room: <strong id="currentRoomId"></strong></span>
|
<span style="font-size: clamp(0.75rem, 3vw, 0.85rem);">Room: <strong id="currentRoomId"></strong></span>
|
||||||
<span id="messageCounter" style="font-size: clamp(0.75rem, 3vw, 0.85rem);">Messages: 0/256</span>
|
<span id="messageCounter" style="font-size: clamp(0.75rem, 3vw, 0.85rem);">Messages: 0/256</span>
|
||||||
|
</div>
|
||||||
|
<div class="room-details" style="min-width: auto; flex: 1;">
|
||||||
|
<span id="userCounter" style="font-size: clamp(0.75rem, 3vw, 0.85rem);">Users: 0</span>
|
||||||
|
<span id="encryptionStatus" style="font-size: clamp(0.75rem, 3vw, 0.85rem);">🔐 Encrypted</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="room-details" style="min-width: auto; flex: 1;">
|
|
||||||
<span id="userCounter" style="font-size: clamp(0.75rem, 3vw, 0.85rem);">Users: 0</span>
|
|
||||||
<span id="encryptionStatus" style="font-size: clamp(0.75rem, 3vw, 0.85rem);">🔐 Encrypted</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- wrapper for scroll -->
|
<!-- wrapper for scroll -->
|
||||||
<div class="messages-wrapper" id="messagesWrapper" style="padding: 0.75rem; padding-bottom: 80px; flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; scroll-behavior: smooth;">
|
<div class="messages-wrapper" id="messagesWrapper" style="padding: 0.75rem; padding-bottom: 80px; flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; scroll-behavior: smooth;">
|
||||||
<div class="messages-container" id="messagesContainer" role="log" aria-live="polite" aria-label="Chat messages">
|
<div class="messages-container" id="messagesContainer" role="log" aria-live="polite" aria-label="Chat messages">
|
||||||
<!-- Messages will be inserted here -->
|
<!-- Messages will be inserted here -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="input-section" id="inputSection" style="display: none; padding: 0.6rem 0.75rem; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000; background: rgba(20, 20, 20, 0.98); backdrop-filter: blur(10px); border-top: 1px solid #333; box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);">
|
<div class="input-section" id="inputSection" style="display: none; padding: 0.6rem 0.75rem; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000; background: rgba(20, 20, 20, 0.98); backdrop-filter: blur(10px); border-top: 1px solid #333; box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);">
|
||||||
<div class="input-container" style="gap: 0.4rem; max-width: 100%;">
|
<div class="input-container" style="gap: 0.4rem; max-width: 100%;">
|
||||||
@@ -133,392 +181,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="security-indicator" id="securityIndicator" style="display: none; bottom: calc(0.75rem + 80px); right: 15px; padding: 0.4rem 0.8rem; font-size: clamp(0.7rem, 3vw, 0.8rem); z-index: 999;">
|
<!-- <div class="security-indicator" id="securityIndicator" style="display: none; bottom: calc(0.75rem + 80px); right: 15px; padding: 0.4rem 0.8rem; font-size: clamp(0.7rem, 3vw, 0.8rem); z-index: 999;">
|
||||||
🔒 End-to-End Encrypted
|
🔒 End-to-End Encrypted
|
||||||
</div>
|
</div>
|
||||||
|
-->
|
||||||
<script>
|
|
||||||
// Mobile keyboard handling
|
|
||||||
let initialViewportHeight = window.innerHeight;
|
|
||||||
|
|
||||||
function adjustForKeyboard() {
|
|
||||||
const currentHeight = window.visualViewport ? window.visualViewport.height : window.innerHeight;
|
|
||||||
const diff = initialViewportHeight - currentHeight;
|
|
||||||
const messagesWrapper = document.getElementById('messagesWrapper');
|
|
||||||
|
|
||||||
if (diff > 150) { // Keyboard is likely open
|
|
||||||
keyboardOpen = true;
|
|
||||||
if (messagesWrapper) {
|
|
||||||
messagesWrapper.style.paddingBottom = '140px'; // Even more padding for mobile keyboard
|
|
||||||
// Reset scroll state and aggressively scroll to bottom
|
|
||||||
userScrolled = false;
|
|
||||||
|
|
||||||
// Multiple scroll attempts with increasing delays
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
|
||||||
}, 100);
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
|
||||||
}, 250);
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
|
||||||
}, 400);
|
|
||||||
|
|
||||||
// iOS often needs even more attempts
|
|
||||||
if (isIOS) {
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200;
|
|
||||||
}, 600);
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200;
|
|
||||||
}, 800);
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200;
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
keyboardOpen = false;
|
|
||||||
if (messagesWrapper) {
|
|
||||||
messagesWrapper.style.paddingBottom = '80px';
|
|
||||||
// Scroll to bottom when keyboard closes too
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!userScrolled) {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100;
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!userScrolled) {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100;
|
|
||||||
}
|
|
||||||
}, 400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen for viewport changes (keyboard open/close)
|
|
||||||
if (window.visualViewport) {
|
|
||||||
window.visualViewport.addEventListener('resize', adjustForKeyboard);
|
|
||||||
} else {
|
|
||||||
window.addEventListener('resize', adjustForKeyboard);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mobile-optimized focus handling for textarea
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const messageInput = document.getElementById('messageInput');
|
|
||||||
if (messageInput) {
|
|
||||||
messageInput.addEventListener('focus', function() {
|
|
||||||
// Reset scroll state when focusing input
|
|
||||||
userScrolled = false;
|
|
||||||
// Multiple timeout attempts for different mobile browsers
|
|
||||||
setTimeout(adjustForKeyboard, 200);
|
|
||||||
setTimeout(adjustForKeyboard, 400);
|
|
||||||
if (isMobile) {
|
|
||||||
setTimeout(adjustForKeyboard, 600);
|
|
||||||
setTimeout(() => {
|
|
||||||
const messagesWrapper = document.getElementById('messagesWrapper');
|
|
||||||
if (messagesWrapper) {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200;
|
|
||||||
}
|
|
||||||
}, 800);
|
|
||||||
// Additional scroll attempt for stubborn mobile browsers
|
|
||||||
setTimeout(() => {
|
|
||||||
const messagesWrapper = document.getElementById('messagesWrapper');
|
|
||||||
if (messagesWrapper) {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250;
|
|
||||||
}
|
|
||||||
}, 1200);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
messageInput.addEventListener('blur', function() {
|
|
||||||
setTimeout(adjustForKeyboard, 200);
|
|
||||||
setTimeout(adjustForKeyboard, 400);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle input events for better mobile experience
|
|
||||||
messageInput.addEventListener('input', function() {
|
|
||||||
if (isMobile && !userScrolled) {
|
|
||||||
setTimeout(() => {
|
|
||||||
scrollToBottom(false);
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mobile-optimized auto-scroll functionality
|
|
||||||
let isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768;
|
|
||||||
let isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
|
||||||
let keyboardOpen = false;
|
|
||||||
|
|
||||||
function scrollToBottom(smooth = true) {
|
|
||||||
const messagesWrapper = document.getElementById('messagesWrapper');
|
|
||||||
if (messagesWrapper) {
|
|
||||||
// On mobile, especially iOS, direct scrollTop assignment is more reliable
|
|
||||||
if (isMobile) {
|
|
||||||
// Different scroll behavior based on keyboard state
|
|
||||||
const extraPadding = keyboardOpen ? 200 : 100;
|
|
||||||
const targetScroll = messagesWrapper.scrollHeight + extraPadding;
|
|
||||||
messagesWrapper.scrollTop = targetScroll;
|
|
||||||
|
|
||||||
// Multiple aggressive scroll attempts for mobile reliability
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + extraPadding;
|
|
||||||
}, 50);
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + extraPadding;
|
|
||||||
}, 150);
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + extraPadding;
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
// Extra attempts when just viewing (no keyboard)
|
|
||||||
if (!keyboardOpen) {
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
|
||||||
}, 500);
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
|
||||||
}, 750);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final iOS-specific attempt
|
|
||||||
if (isIOS) {
|
|
||||||
const finalPadding = keyboardOpen ? 250 : 200;
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + finalPadding;
|
|
||||||
}, keyboardOpen ? 500 : 1000);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (smooth) {
|
|
||||||
messagesWrapper.scrollTo({
|
|
||||||
top: messagesWrapper.scrollHeight,
|
|
||||||
behavior: 'smooth'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to check if user is near bottom of messages
|
|
||||||
function isNearBottom() {
|
|
||||||
const messagesWrapper = document.getElementById('messagesWrapper');
|
|
||||||
if (!messagesWrapper) return true;
|
|
||||||
|
|
||||||
// Larger threshold for mobile to account for touch scrolling momentum
|
|
||||||
const threshold = isMobile ? 150 : 100;
|
|
||||||
return messagesWrapper.scrollHeight - messagesWrapper.scrollTop - messagesWrapper.clientHeight < threshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mobile scroll state management
|
|
||||||
let lastScrollTop = 0;
|
|
||||||
let userScrolled = false;
|
|
||||||
let scrollTimeout;
|
|
||||||
let touchStartY = 0;
|
|
||||||
let isScrolling = false;
|
|
||||||
|
|
||||||
// Detect user scroll with mobile optimizations
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const messagesWrapper = document.getElementById('messagesWrapper');
|
|
||||||
if (messagesWrapper) {
|
|
||||||
|
|
||||||
// Handle touch events for better mobile detection
|
|
||||||
messagesWrapper.addEventListener('touchstart', function(e) {
|
|
||||||
touchStartY = e.touches[0].clientY;
|
|
||||||
isScrolling = false;
|
|
||||||
clearTimeout(scrollTimeout);
|
|
||||||
}, { passive: true });
|
|
||||||
|
|
||||||
messagesWrapper.addEventListener('touchmove', function(e) {
|
|
||||||
if (!isScrolling) {
|
|
||||||
isScrolling = true;
|
|
||||||
const touchY = e.touches[0].clientY;
|
|
||||||
const deltaY = touchStartY - touchY;
|
|
||||||
|
|
||||||
// If user is swiping up (scrolling up), mark as user scrolled
|
|
||||||
if (deltaY < -10 && !isNearBottom()) {
|
|
||||||
userScrolled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, { passive: true });
|
|
||||||
|
|
||||||
messagesWrapper.addEventListener('touchend', function() {
|
|
||||||
// Check scroll position after touch ends with delay for momentum scrolling
|
|
||||||
setTimeout(() => {
|
|
||||||
if (isNearBottom()) {
|
|
||||||
userScrolled = false;
|
|
||||||
}
|
|
||||||
isScrolling = false;
|
|
||||||
}, 300);
|
|
||||||
}, { passive: true });
|
|
||||||
|
|
||||||
// Regular scroll event with debouncing for mobile
|
|
||||||
messagesWrapper.addEventListener('scroll', function() {
|
|
||||||
clearTimeout(scrollTimeout);
|
|
||||||
scrollTimeout = setTimeout(() => {
|
|
||||||
const currentScrollTop = messagesWrapper.scrollTop;
|
|
||||||
const maxScroll = messagesWrapper.scrollHeight - messagesWrapper.clientHeight;
|
|
||||||
|
|
||||||
// More lenient detection for mobile
|
|
||||||
const upScrollThreshold = isMobile ? 30 : 50;
|
|
||||||
|
|
||||||
// If user scrolled up manually, don't auto-scroll for new messages
|
|
||||||
if (currentScrollTop < lastScrollTop && currentScrollTop < maxScroll - upScrollThreshold) {
|
|
||||||
userScrolled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user scrolled to near bottom, resume auto-scrolling
|
|
||||||
if (isNearBottom()) {
|
|
||||||
userScrolled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastScrollTop = currentScrollTop;
|
|
||||||
}, isMobile ? 150 : 50); // Longer debounce for mobile
|
|
||||||
}, { passive: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mobile-optimized message adding function
|
|
||||||
function addMessageWithAutoScroll(messageElement) {
|
|
||||||
const messagesContainer = document.getElementById('messagesContainer');
|
|
||||||
if (messagesContainer && messageElement) {
|
|
||||||
messagesContainer.appendChild(messageElement);
|
|
||||||
|
|
||||||
// Only auto-scroll if user hasn't manually scrolled up
|
|
||||||
if (!userScrolled) {
|
|
||||||
const messagesWrapper = document.getElementById('messagesWrapper');
|
|
||||||
if (messagesWrapper) {
|
|
||||||
// Immediate first scroll
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100;
|
|
||||||
|
|
||||||
// Progressive scroll attempts with different padding
|
|
||||||
setTimeout(() => {
|
|
||||||
const padding = keyboardOpen ? 200 : 150;
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding;
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
const padding = keyboardOpen ? 200 : 150;
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding;
|
|
||||||
}, 250);
|
|
||||||
|
|
||||||
// Extra attempts when just viewing (no keyboard)
|
|
||||||
if (!keyboardOpen && isMobile) {
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200;
|
|
||||||
}, 400);
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250;
|
|
||||||
}, 600);
|
|
||||||
// Final attempt for stubborn browsers
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 300;
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// iOS-specific final attempt
|
|
||||||
if (isIOS) {
|
|
||||||
const finalPadding = keyboardOpen ? 250 : 300;
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + finalPadding;
|
|
||||||
}, keyboardOpen ? 600 : 1200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force scroll with mobile optimizations
|
|
||||||
function forceScrollToBottom() {
|
|
||||||
userScrolled = false;
|
|
||||||
const messagesWrapper = document.getElementById('messagesWrapper');
|
|
||||||
if (messagesWrapper) {
|
|
||||||
// Immediate aggressive scroll
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150;
|
|
||||||
}, 100);
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200;
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
// Extra attempts for iOS
|
|
||||||
if (isIOS) {
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250;
|
|
||||||
}, 500);
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250;
|
|
||||||
}, 700);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mobile-optimized mutation observer
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const messagesContainer = document.getElementById('messagesContainer');
|
|
||||||
if (messagesContainer) {
|
|
||||||
// Create a MutationObserver to watch for new messages
|
|
||||||
const observer = new MutationObserver(function(mutations) {
|
|
||||||
mutations.forEach(function(mutation) {
|
|
||||||
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
|
||||||
// Check if any added nodes are message elements
|
|
||||||
const hasNewMessage = Array.from(mutation.addedNodes).some(node =>
|
|
||||||
node.nodeType === Node.ELEMENT_NODE &&
|
|
||||||
(node.classList.contains('message-group') || node.querySelector('.message-group'))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasNewMessage && !userScrolled) {
|
|
||||||
const messagesWrapper = document.getElementById('messagesWrapper');
|
|
||||||
if (messagesWrapper) {
|
|
||||||
// Immediate scroll
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
const padding = keyboardOpen ? 200 : 150;
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding;
|
|
||||||
}, 100);
|
|
||||||
setTimeout(() => {
|
|
||||||
const padding = keyboardOpen ? 250 : 200;
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding;
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
// Extra scrolling when just viewing (no keyboard)
|
|
||||||
if (!keyboardOpen && isMobile) {
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250;
|
|
||||||
}, 500);
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 300;
|
|
||||||
}, 800);
|
|
||||||
// Final aggressive attempt for viewing mode
|
|
||||||
setTimeout(() => {
|
|
||||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 350;
|
|
||||||
}, 1200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start observing
|
|
||||||
observer.observe(messagesContainer, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expose functions globally so your existing JavaScript can use them
|
|
||||||
window.ByteChat = window.ByteChat || {};
|
|
||||||
window.ByteChat.scrollToBottom = scrollToBottom;
|
|
||||||
window.ByteChat.forceScrollToBottom = forceScrollToBottom;
|
|
||||||
window.ByteChat.addMessageWithAutoScroll = addMessageWithAutoScroll;
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user