diff --git a/resources/js/chat-room.js b/resources/js/chat-room.js index 765923d..5518539 100644 --- a/resources/js/chat-room.js +++ b/resources/js/chat-room.js @@ -64,8 +64,10 @@ export { bindChatToast } from "./chat-room/toast.js"; export { bindChatComposerControls, setChatComposerAction } from "./chat-room/composer.js"; export { isExpiredChatImageMessage, + isAutoScrollEnabled, localClearScreen, scrollChatToBottom, + setAutoScrollEnabled, syncAutoScrollControls, toggleAutoScroll, } from "./chat-room/message-utils.js"; @@ -319,8 +321,10 @@ if (typeof window !== "undefined") { bindChatComposerControls, setChatComposerAction, isExpiredChatImageMessage, + isAutoScrollEnabled, localClearScreen, scrollChatToBottom, + setAutoScrollEnabled, syncAutoScrollControls, toggleAutoScroll, bindInstantHoverTooltip, @@ -592,8 +596,10 @@ if (typeof window !== "undefined") { // ── 静态核心模块 window 挂载 ── window.escapeHtml = escapeHtml; window.isExpiredChatImageMessage = isExpiredChatImageMessage; + window.isChatAutoScrollEnabled = isAutoScrollEnabled; window.localClearScreen = localClearScreen; window.normalizeSafeChatUrl = normalizeSafeChatUrl; + window.setChatAutoScrollEnabled = setAutoScrollEnabled; window.setAction = setChatComposerAction; window.syncAutoScrollControls = syncAutoScrollControls; diff --git a/resources/js/chat-room/appointment-announcement.js b/resources/js/chat-room/appointment-announcement.js index dbd4c7c..8b5a320 100644 --- a/resources/js/chat-room/appointment-announcement.js +++ b/resources/js/chat-room/appointment-announcement.js @@ -1,6 +1,7 @@ // 聊天室任命/撤销公告监听,负责渲染大卡片和频道内系统提示。 import { escapeHtml } from "./html.js"; +import { isAutoScrollEnabled, scrollChatToBottom } from "./message-utils.js"; const APPOINTMENT_PHRASES = [ "望再接再厉,大展宏图,为大家服务!", @@ -132,7 +133,7 @@ function appendAppointmentMessage(container, message) { } container.appendChild(message); - container.scrollTop = container.scrollHeight; + scrollChatToBottom(container, isAutoScrollEnabled); } /** diff --git a/resources/js/chat-room/chat-events.js b/resources/js/chat-room/chat-events.js index b47b7bc..5dab2f3 100644 --- a/resources/js/chat-room/chat-events.js +++ b/resources/js/chat-room/chat-events.js @@ -4,6 +4,7 @@ import { escapeHtml, normalizeSafeChatUrl } from "./html.js"; import { normalizeDailyStatus } from "./preferences-status.js"; import { enqueueChatMessage } from "./message-renderer.js"; +import { isAutoScrollEnabled, scrollChatToBottom } from "./message-utils.js"; // ── 事件注册标记 ── let chatEventsBound = false; @@ -19,6 +20,16 @@ function getState() { return window.chatState; } +/** + * 在开启自动滚屏时把指定聊天窗格滚动到底部。 + * + * @param {HTMLElement|null|undefined} container 聊天消息容器 + * @returns {void} + */ +function scrollWhenEnabled(container) { + scrollChatToBottom(container, isAutoScrollEnabled); +} + /** * 启动 WebSocket 初始化(DOMContentLoaded 之后调用)。 */ @@ -79,7 +90,7 @@ function handleMutedEvent(e) { : (state?.container); if (targetContainer) { targetContainer.appendChild(div); - targetContainer.scrollTop = targetContainer.scrollHeight; + scrollWhenEnabled(targetContainer); } if (isMe && d.mute_time > 0) { @@ -99,7 +110,7 @@ function handleMutedEvent(e) { const say2 = document.getElementById("say2"); if (say2) { say2.appendChild(unmuteDiv); - say2.scrollTop = say2.scrollHeight; + scrollWhenEnabled(say2); } }, d.mute_time * 60 * 1000); } @@ -148,7 +159,7 @@ function setupScreenClearedListener() { sysDiv.innerHTML = `🧹 管理员 ${safeOperator} 已执行全员清屏(${timeStr})`; if (say1) { say1.appendChild(sysDiv); - say1.scrollTop = say1.scrollHeight; + scrollWhenEnabled(say1); } }); } @@ -205,7 +216,7 @@ function setupChangelogPublishedListener() { const say1 = document.getElementById("chat-messages-container"); if (say1) { say1.appendChild(sysDiv); - say1.scrollTop = say1.scrollHeight; + scrollWhenEnabled(say1); } }); } @@ -255,7 +266,7 @@ function setupGomokuInviteListener() { const say1 = document.getElementById("chat-messages-container"); if (say1) { say1.appendChild(div); - say1.scrollTop = say1.scrollHeight; + scrollWhenEnabled(say1); } if (!isSelf) { @@ -294,7 +305,7 @@ function setupGomokuInviteListener() { const say1 = document.getElementById("chat-messages-container"); if (say1) { say1.appendChild(div); - say1.scrollTop = say1.scrollHeight; + scrollWhenEnabled(say1); } }); } diff --git a/resources/js/chat-room/composer.js b/resources/js/chat-room/composer.js index 3def3c8..f5f4dea 100644 --- a/resources/js/chat-room/composer.js +++ b/resources/js/chat-room/composer.js @@ -1,6 +1,8 @@ // 聊天输入区完整逻辑:发送消息、草稿管理、IME 防重、神秘箱子暗号拦截。 // 从 Blade 内联脚本 scripts.blade.php 迁移至 Vite 模块。 +import { isAutoScrollEnabled, scrollChatToBottom } from "./message-utils.js"; + let chatComposerEventsBound = false; function csrf() { @@ -165,7 +167,7 @@ async function sendMessage(e) { const say2 = document.getElementById("say2"); if (say2) { say2.appendChild(muteDiv); - say2.scrollTop = say2.scrollHeight; + scrollChatToBottom(say2, isAutoScrollEnabled); } if (state) { state.isSending = false; diff --git a/resources/js/chat-room/marriage-modals.js b/resources/js/chat-room/marriage-modals.js index 66bbe7e..6eeaf99 100644 --- a/resources/js/chat-room/marriage-modals.js +++ b/resources/js/chat-room/marriage-modals.js @@ -1,5 +1,7 @@ // 婚姻弹窗辅助入口,承接从 marriage-modals.blade.php 迁移出的全局函数。 +import { isAutoScrollEnabled, scrollChatToBottom } from "./message-utils.js"; + /** * 向聊天主窗口追加一条婚姻系统公告,允许传入受控 HTML 按钮。 * @@ -17,7 +19,7 @@ export function appendSystemMessage(html) { div.style.cssText = "background:linear-gradient(135deg,#fdf4ff,#fce7f3); border-left:3px solid #ec4899; border-radius:6px; padding:5px 12px; margin:3px 0; font-size:13px; line-height:1.6;"; div.innerHTML = `${html}`; container.appendChild(div); - container.scrollTop = container.scrollHeight; + scrollChatToBottom(container, isAutoScrollEnabled); } /** diff --git a/resources/js/chat-room/message-utils.js b/resources/js/chat-room/message-utils.js index 0103e84..8f9a998 100644 --- a/resources/js/chat-room/message-utils.js +++ b/resources/js/chat-room/message-utils.js @@ -75,10 +75,46 @@ export function localClearScreen(roomId = window.chatContext?.roomId, maxMessage if (publicPane) { publicPane.appendChild(notice); - publicPane.scrollTop = publicPane.scrollHeight; + scrollChatToBottom(publicPane, isAutoScrollEnabled); } } +/** + * 读取当前是否允许聊天窗口自动滚屏。 + * + * @returns {boolean} + */ +export function isAutoScrollEnabled() { + const state = window.chatState; + + if (state && typeof state.autoScroll !== "undefined") { + return Boolean(state.autoScroll); + } + + const checkbox = document.getElementById("auto_scroll"); + + return checkbox ? Boolean(checkbox.checked) : true; +} + +/** + * 写入自动滚屏状态,并同步页面复选框和状态文字。 + * + * @param {boolean} enabled 是否开启自动滚屏 + * @returns {boolean} + */ +export function setAutoScrollEnabled(enabled) { + const nextEnabled = Boolean(enabled); + const state = window.chatState; + + if (state) { + state.autoScroll = nextEnabled; + } + + syncAutoScrollControls(nextEnabled); + + return nextEnabled; +} + /** * 同步自动滚屏复选框与状态文字。 * @@ -111,6 +147,8 @@ export function toggleAutoScroll(getCurrent, setCurrent) { if (typeof setCurrent === "function") { setCurrent(nextEnabled); + } else { + setAutoScrollEnabled(nextEnabled); } syncAutoScrollControls(nextEnabled);