🐛 | 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"]',
];
let textarea = null;
let editorEl = null;
for (const sel of selectors) {
textarea = document.querySelector(sel);
if (textarea) break;
editorEl = document.querySelector(sel);
if (editorEl) break;
}
if (!textarea) return false;
if (!editorEl) return false;
// Determine the native input element
let nativeInput;
const isContentEditable =
textarea.tagName !== "TEXTAREA" &&
(textarea.isContentEditable || textarea.getAttribute("contenteditable") === "true");
editorEl.tagName !== "TEXTAREA" &&
(editorEl.isContentEditable || editorEl.getAttribute("contenteditable") === "true");
if (textarea.tagName === "TEXTAREA") {
if (editorEl.tagName === "TEXTAREA") {
// Classic Discord: direct textarea element
nativeInput = textarea;
nativeInput = editorEl;
} else if (isContentEditable) {
// Modern Discord: Slate/contenteditable div - use it directly
nativeInput = textarea;
nativeInput = editorEl;
} else {
// Look for a nested textarea (older layout)
nativeInput = textarea.querySelector("textarea");
nativeInput = editorEl.querySelector("textarea");
}
if (!nativeInput) return false;
// Set the value
// ── Set the value ───────────────────────────────────────────
nativeInput.focus();
if (nativeInput.tagName === "TEXTAREA") {
// Classic textarea input
nativeInput.value = text;
nativeInput.dispatchEvent(new Event("input", { bubbles: true, cancelable: true }));
} else {
// ContentEditable div (Slate editor / modern Discord)
// Clear existing content and insert new text
nativeInput.textContent = "";
nativeInput.focus();
// ContentEditable div modern Discord Slate editor
// Slate listens for "beforeinput" events, not plain DOM mutations.
// We select all existing content, then dispatch beforeinput so
// 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 {
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);
} catch (_) {
// Fallback: set textContent directly
// Last resort: set textContent directly
nativeInput.textContent = text;
}
}
// Dispatch an input event so React picks up the change
const nativeInputEv = new Event("input", { bubbles: true, cancelable: true });
nativeInput.dispatchEvent(nativeInputEv);
// 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 }));
// 4. Always dispatch input — React-controlled components often
// listen for this in addition to beforeinput.
nativeInput.dispatchEvent(new Event("input", { bubbles: true }));
}
return true;