🐛 | Fix message input detection and Slate editor insertion

This commit is contained in:
2026-05-19 02:46:45 +03:00
parent ea368477c4
commit 9eb6b55d9e

View File

@@ -83,62 +83,90 @@ function setMessageInput(text) {
'[class*="chat"] [class*="channelTextArea"] [role="textbox"]', '[class*="chat"] [class*="channelTextArea"] [role="textbox"]',
]; ];
let textarea = null; let editorEl = null;
for (const sel of selectors) { for (const sel of selectors) {
textarea = document.querySelector(sel); editorEl = document.querySelector(sel);
if (textarea) break; if (editorEl) break;
} }
if (!textarea) return false; if (!editorEl) return false;
// Determine the native input element // Determine the native input element
let nativeInput; let nativeInput;
const isContentEditable = const isContentEditable =
textarea.tagName !== "TEXTAREA" && editorEl.tagName !== "TEXTAREA" &&
(textarea.isContentEditable || textarea.getAttribute("contenteditable") === "true"); (editorEl.isContentEditable || editorEl.getAttribute("contenteditable") === "true");
if (textarea.tagName === "TEXTAREA") { if (editorEl.tagName === "TEXTAREA") {
// Classic Discord: direct textarea element // Classic Discord: direct textarea element
nativeInput = textarea; nativeInput = editorEl;
} else if (isContentEditable) { } else if (isContentEditable) {
// Modern Discord: Slate/contenteditable div - use it directly // Modern Discord: Slate/contenteditable div - use it directly
nativeInput = textarea; nativeInput = editorEl;
} else { } else {
// Look for a nested textarea (older layout) // Look for a nested textarea (older layout)
nativeInput = textarea.querySelector("textarea"); nativeInput = editorEl.querySelector("textarea");
} }
if (!nativeInput) return false; if (!nativeInput) return false;
// Set the value // ── Set the value ───────────────────────────────────────────
nativeInput.focus(); nativeInput.focus();
if (nativeInput.tagName === "TEXTAREA") { if (nativeInput.tagName === "TEXTAREA") {
// Classic textarea input // Classic textarea input
nativeInput.value = text; nativeInput.value = text;
nativeInput.dispatchEvent(new Event("input", { bubbles: true, cancelable: true }));
} else { } else {
// ContentEditable div (Slate editor / modern Discord) // ContentEditable div modern Discord Slate editor
// Clear existing content and insert new text // Slate listens for "beforeinput" events, not plain DOM mutations.
nativeInput.textContent = ""; // We select all existing content, then dispatch beforeinput so
nativeInput.focus(); // Slate handles the replacement through its own React pipeline.
// Use execCommand as fallback for Slate-based editors // 1. Select all existing content so the insert replaces everything
const sel = window.getSelection();
if (sel) {
try { try {
const range = document.createRange();
range.selectNodeContents(nativeInput);
sel.removeAllRanges();
sel.addRange(range);
} catch (_) { /* selection API may fail on some elements */ }
}
// 2. Dispatch beforeinput — this is what Slate's withReact
// plugin hooks into to update the editor's React state.
// dispatchEvent returns FALSE when the event was cancelled
// (Slate calls preventDefault when it handles the event).
// TRUE means nobody cancelled -> we need the fallback.
let needsFallback = true;
try {
const notCancelled = nativeInput.dispatchEvent(new InputEvent("beforeinput", {
inputType: "insertText",
data: text,
bubbles: true,
cancelable: true,
}));
needsFallback = notCancelled;
} catch (_) {
// InputEvent constructor may not be available (old Electron)
}
// 3. Fallback: if beforeinput wasn't cancelled by any handler
// use execCommand which works on older Discord layouts.
if (needsFallback) {
try {
document.execCommand("selectAll", false, null);
document.execCommand("insertText", false, text); document.execCommand("insertText", false, text);
} catch (_) { } catch (_) {
// Fallback: set textContent directly // Last resort: set textContent directly
nativeInput.textContent = text; nativeInput.textContent = text;
} }
} }
// Dispatch an input event so React picks up the change // 4. Always dispatch input — React-controlled components often
const nativeInputEv = new Event("input", { bubbles: true, cancelable: true }); // listen for this in addition to beforeinput.
nativeInput.dispatchEvent(nativeInputEv); nativeInput.dispatchEvent(new Event("input", { bubbles: true }));
// Also dispatch on the outer element if it differs and is contenteditable
if (textarea !== nativeInput && textarea.isContentEditable) {
textarea.textContent = text;
textarea.dispatchEvent(new Event("input", { bubbles: true }));
} }
return true; return true;