Prevents "/" from becoming a command chip and hides the slash-command palette popover
// ==UserScript==
// @name Le Chat - Disable Slash Command
// @namespace https://chat.mistral.ai/
// @version 1.0.0
// @description Prevents "/" from becoming a command chip and hides the slash-command palette popover
// @match https://chat.mistral.ai/*
// @run-at document-start
// @grant none
// @icon https://chat.mistral.ai/favicons/favicon.ico
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const ZWSP = '\u200B';
const SAFE_SLASH = '/' + ZWSP;
function isEditable(el) {
if (!el) return false;
const tag = (el.tagName || '').toLowerCase();
if (tag === 'textarea') return true;
if (tag === 'input') return true;
if (el.isContentEditable) return true;
return !!el.closest?.('[contenteditable="true"]');
}
function insertText(text) {
// The most compatible method in contenteditable editors
document.execCommand('insertText', false, text);
}
function getTextBeforeCursor(maxChars = 10) {
const sel = window.getSelection();
if (!sel || sel.rangeCount === 0) return '';
const range = sel.getRangeAt(0).cloneRange();
if (!range.collapsed) return '';
const container = range.startContainer;
let root = container.nodeType === 1 ? container : container.parentElement;
root = root?.closest?.('.ProseMirror, [contenteditable="true"]') || root;
if (!root) return '';
const r = document.createRange();
r.selectNodeContents(root);
r.setEnd(range.startContainer, range.startOffset);
const txt = r.toString();
return txt.slice(-maxChars);
}
function deleteSafeSlashIfNeeded(e) {
const before = getTextBeforeCursor(4);
if (!before.endsWith(SAFE_SLASH)) return false;
e.preventDefault();
const sel = window.getSelection();
if (!sel) return true;
try {
sel.modify('extend', 'backward', 'character');
sel.modify('extend', 'backward', 'character');
document.execCommand('delete', false);
} catch {
document.execCommand('delete', false);
document.execCommand('delete', false);
}
return true;
}
// 1) Input interception: make “/” “safe”
document.addEventListener('beforeinput', (e) => {
if (!isEditable(e.target)) return;
if (e.inputType === 'insertText' && e.data === '/') {
e.preventDefault();
insertText(SAFE_SLASH);
return;
}
if (e.inputType === 'deleteContentBackward') {
deleteSafeSlashIfNeeded(e);
}
}, true);
// 2) Fallback
document.addEventListener('keydown', (e) => {
if (!isEditable(e.target)) return;
if (e.key === '/' && !e.ctrlKey && !e.metaKey && !e.altKey) {
e.preventDefault();
insertText(SAFE_SLASH);
return;
}
if (e.key === 'Backspace') {
deleteSafeSlashIfNeeded(e);
}
}, true);
// 3) Targeted hide: Hide only the “slash command palette” popover (cmdk)
function isSlashPaletteWrapper(wrapper) {
if (!wrapper?.querySelector) return false;
// The profile menu is role="menu" / data-radix-menu-content – we will NOT touch that.
if (wrapper.querySelector('[data-radix-menu-content][role="menu"]')) return false;
// The Slash palette in your “after” HTML is cmdk-based and typically contains beta-* values
// (e.g., beta-websearch, beta-trampoline).:contentReference[oaicite:4]{index=4}
const hasCmdk = !!wrapper.querySelector('[cmdk-root]');
const hasKnownItems = !!wrapper.querySelector('[cmdk-item][data-value="beta-websearch"],[cmdk-item][data-value="beta-trampoline"]');
return hasCmdk && hasKnownItems;
}
function hideIfSlashPalette(node) {
if (!node || node.nodeType !== 1) return;
const el = /** @type {Element} */ (node);
// The node itself can be a wrapper; otherwise, find wrappers under
const wrappers = [];
if (el.matches?.('[data-radix-popper-content-wrapper]')) wrappers.push(el);
el.querySelectorAll?.('[data-radix-popper-content-wrapper]').forEach(w => wrappers.push(w));
for (const w of wrappers) {
if (isSlashPaletteWrapper(w)) {
w.style.display = 'none';
w.style.visibility = 'hidden';
w.style.pointerEvents = 'none';
}
}
}
function startObserver() {
const obs = new MutationObserver((mutations) => {
for (const m of mutations) {
for (const n of m.addedNodes) hideIfSlashPalette(n);
}
});
obs.observe(document.documentElement, { childList: true, subtree: true });
// cleanup if it already exists
hideIfSlashPalette(document.documentElement);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', startObserver, { once: true });
} else {
startObserver();
}
})();