// 聊天输入区完整逻辑:发送消息、草稿管理、IME 防重、神秘箱子暗号拦截。 // 从 Blade 内联脚本 scripts.blade.php 迁移至 Vite 模块。 let chatComposerEventsBound = false; function csrf() { return document.querySelector('meta[name="csrf-token"]')?.content ?? ""; } function getState() { return window.chatState; } function getDraftStorageKey() { return `chat_draft_${window.chatContext?.roomId ?? '0'}_${window.chatContext?.userId ?? '0'}`; } // ── 草稿管理 ── function persistChatDraft(value = null) { try { const contentInput = document.getElementById("content"); const draft = value ?? contentInput?.value ?? ""; if (draft === "") { sessionStorage.removeItem(getDraftStorageKey()); return; } sessionStorage.setItem(getDraftStorageKey(), draft); } catch (_) { // 会话存储不可用时静默降级 } } function loadChatDraft() { try { return sessionStorage.getItem(getDraftStorageKey()) || ""; } catch (_) { return ""; } } // ── 消息发送 ── /** * 将当前输入区状态整理为一份稳定快照。 */ function collectChatComposerState() { const contentInput = document.getElementById("content"); const submitBtn = document.getElementById("send-btn"); const imageInput = document.getElementById("chat_image"); const toUserSelect = document.getElementById("to_user"); const actionSelect = document.getElementById("action"); const fontColorInput = document.getElementById("font_color"); const secretCheckbox = document.getElementById("is_secret"); const contentRaw = contentInput?.value ?? ""; const selectedImage = imageInput?.files?.[0] ?? null; return { contentInput, submitBtn, imageInput, contentRaw, content: contentRaw.trim(), selectedImage, toUser: toUserSelect?.value || "大家", action: actionSelect?.value || "", fontColor: fontColorInput?.value || "", isSecret: Boolean(secretCheckbox?.checked), }; } /** * 基于当前聊天快照构造稳定的 multipart 请求体。 */ function buildChatMessageFormData(composerState) { const formData = new FormData(); formData.append("content", composerState.contentRaw); formData.append("to_user", composerState.toUser); formData.append("action", composerState.action); formData.append("font_color", composerState.fontColor); if (composerState.isSecret) { formData.append("is_secret", "1"); } if (composerState.selectedImage) { formData.append("image", composerState.selectedImage); } return formData; } /** * 处理聊天图片选择后的前端状态展示。 */ function handleChatImageSelected(input) { const file = input?.files?.[0] ?? null; if (!file) return; // 用户选择图片后,立即触发自动发送 sendMessage(null); } /** * 清理当前选中的聊天图片。 */ function clearSelectedChatImage(resetInput = false) { const imageInput = document.getElementById("chat_image"); if (resetInput && imageInput) { imageInput.value = ""; } } /** * 页面从后台恢复后,同步草稿、图片提示和发送锁状态。 */ function syncChatComposerAfterResume() { const state = getState(); const contentInput = document.getElementById("content"); if (!contentInput) return; const savedDraft = loadChatDraft(); if (contentInput.value === "" && savedDraft !== "") { contentInput.value = savedDraft; } else if (contentInput.value !== "") { persistChatDraft(contentInput.value); } const imageInput = document.getElementById("chat_image"); if (!imageInput?.files?.length) { clearSelectedChatImage(); } if (state) { state.imeComposing = false; } if (state && state.isSending && Date.now() - state.sendStartedAt > 15000) { const submitBtn = document.getElementById("send-btn"); if (submitBtn) submitBtn.disabled = false; state.isSending = false; state.sendStartedAt = 0; } } /** * 发送聊天消息(内带防重入锁,避免快速连按 Enter 重复提交)。 */ async function sendMessage(e) { if (e) e.preventDefault(); const state = getState(); if (state?.isSending) return; if (state) { state.isSending = true; state.sendStartedAt = Date.now(); } // 前端禁言检查 if (state && state.isMutedUntil > Date.now()) { const remaining = Math.ceil((state.isMutedUntil - Date.now()) / 1000); const remainMin = Math.ceil(remaining / 60); const muteDiv = document.createElement("div"); muteDiv.className = "msg-line"; muteDiv.innerHTML = `【提示】您正在禁言中,还需等待约 ${remainMin} 分钟(${remaining} 秒)后方可发言。`; const say2 = document.getElementById("say2"); if (say2) { say2.appendChild(muteDiv); say2.scrollTop = say2.scrollHeight; } if (state) { state.isSending = false; state.sendStartedAt = 0; } return; } const composerState = collectChatComposerState(); const { contentInput, submitBtn, content, contentRaw, selectedImage, toUser } = composerState; if (!content && !selectedImage) { contentInput?.focus(); if (state) { state.isSending = false; state.sendStartedAt = 0; } return; } // AI 小助手私聊转发 if (toUser === "AI小班长" && content && typeof window.sendToChatBot === "function") { window.sendToChatBot(content, composerState.isSecret); } // ── 神秘箱子暗号拦截 ── const passcodePattern = /^[A-Z0-9]{4,8}$/; if (!selectedImage && passcodePattern.test(content.trim())) { if (state) { state.isSending = false; state.sendStartedAt = 0; } try { const claimRes = await fetch("/mystery-box/claim", { method: "POST", headers: { "X-CSRF-TOKEN": csrf(), "Content-Type": "application/json", "Accept": "application/json", }, body: JSON.stringify({ passcode: content.trim() }), }); const claimData = await claimRes.json(); if (claimData.ok) { contentInput.value = ""; persistChatDraft(""); contentInput.focus(); window._mysteryBoxActive = false; window._mysteryBoxPasscode = null; const isPositive = (claimData.reward ?? 1) >= 0; window.chatDialog?.alert( claimData.message || "开箱成功!", isPositive ? "🎉 恭喜!" : "☠️ 中了陷阱!", isPositive ? "#10b981" : "#ef4444" ); if (window.__chatUser && claimData.balance !== undefined) { window.__chatUser.jjb = claimData.balance; } return; } } catch (_) { // 网络错误时静默回退正常发送 } } submitBtn.disabled = true; const formData = buildChatMessageFormData({ ...composerState, contentRaw }); try { const response = await fetch(window.chatContext.sendUrl, { method: "POST", headers: { "X-CSRF-TOKEN": csrf(), "Accept": "application/json", }, body: formData, }); const data = await response.json(); if (response.ok && data.status === "success") { contentInput.value = ""; persistChatDraft(""); clearSelectedChatImage(true); contentInput.focus(); } else { window.chatDialog?.alert( "发送失败: " + (data.message || JSON.stringify(data.errors)), "操作失败", "#cc4444" ); } } catch (error) { window.chatDialog?.alert("网络连接错误,消息发送失败!", "网络错误", "#cc4444"); console.error(error); } finally { submitBtn.disabled = false; if (state) { state.isSending = false; state.sendStartedAt = 0; } } } // ── 事件绑定 ── /** * 绑定聊天输入区的所有事件:submit、IME、keydown、草稿、焦点恢复。 */ export function bindChatComposerControls() { if (chatComposerEventsBound || typeof document === "undefined") { return; } chatComposerEventsBound = true; // 表单提交 document.addEventListener("submit", (event) => { const form = event.target; if (!(form instanceof HTMLFormElement) || !form.matches("[data-chat-form]")) { return; } event.preventDefault(); if (typeof window.sendMessage === "function") { void window.sendMessage(event); } }); // 输入框事件绑定 const contentInput = document.getElementById("content"); if (contentInput) { contentInput.addEventListener("input", function () { persistChatDraft(this.value); }); // IME 组词开始 contentInput.addEventListener("compositionstart", () => { const state = getState(); if (state) state.imeComposing = true; }); // IME 组词结束 contentInput.addEventListener("compositionend", () => { setTimeout(() => { const state = getState(); if (state) state.imeComposing = false; }, 10); }); // Enter 发送 contentInput.addEventListener("keydown", function (e) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); const state = getState(); if (state?.imeComposing) return; sendMessage(e); } }); } // 页面恢复事件 syncChatComposerAfterResume(); window.addEventListener("pageshow", syncChatComposerAfterResume); document.addEventListener("visibilitychange", function () { if (document.visibilityState === "visible") { syncChatComposerAfterResume(); } }); window.addEventListener("focus", function () { setTimeout(syncChatComposerAfterResume, 0); }); } /** * 设置聊天动作并把焦点带回输入框。 */ export function setChatComposerAction( action, switchTabHandler = typeof window !== "undefined" ? window.switchTab : null, ) { const actionSelect = document.getElementById("action"); const contentInput = document.getElementById("content"); if (actionSelect) { actionSelect.value = action; } if (typeof switchTabHandler === "function") { switchTabHandler("users"); } if (contentInput) { contentInput.focus(); } } // ── 挂载到 window ── window.sendMessage = sendMessage; window.handleChatImageSelected = handleChatImageSelected; window.collectChatComposerState = collectChatComposerState; window.buildChatMessageFormData = buildChatMessageFormData; window.clearSelectedChatImage = clearSelectedChatImage; window.persistChatDraft = persistChatDraft; window.loadChatDraft = loadChatDraft; window.syncChatComposerAfterResume = syncChatComposerAfterResume; export { sendMessage, handleChatImageSelected, clearSelectedChatImage, collectChatComposerState, buildChatMessageFormData };