🐛 | 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"]',
|
'[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
|
||||||
try {
|
const sel = window.getSelection();
|
||||||
document.execCommand("insertText", false, text);
|
if (sel) {
|
||||||
} catch (_) {
|
try {
|
||||||
// Fallback: set textContent directly
|
const range = document.createRange();
|
||||||
nativeInput.textContent = text;
|
range.selectNodeContents(nativeInput);
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
} catch (_) { /* selection API may fail on some elements */ }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch an input event so React picks up the change
|
// 2. Dispatch beforeinput — this is what Slate's withReact
|
||||||
const nativeInputEv = new Event("input", { bubbles: true, cancelable: true });
|
// plugin hooks into to update the editor's React state.
|
||||||
nativeInput.dispatchEvent(nativeInputEv);
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
// Also dispatch on the outer element if it differs and is contenteditable
|
// 3. Fallback: if beforeinput wasn't cancelled by any handler
|
||||||
if (textarea !== nativeInput && textarea.isContentEditable) {
|
// use execCommand which works on older Discord layouts.
|
||||||
textarea.textContent = text;
|
if (needsFallback) {
|
||||||
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
try {
|
||||||
|
document.execCommand("selectAll", false, null);
|
||||||
|
document.execCommand("insertText", false, text);
|
||||||
|
} catch (_) {
|
||||||
|
// Last resort: set textContent directly
|
||||||
|
nativeInput.textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Always dispatch input — React-controlled components often
|
||||||
|
// listen for this in addition to beforeinput.
|
||||||
|
nativeInput.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user