🐛 | Fix message input detection and Slate editor insertion
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user