Untitled
unknown
plain_text
2 months ago
5.1 kB
51
No Index
// ==UserScript==
// @name Tandem – Focus Only (prep Enter)
// @namespace https://tandem-helper.local
// @version 2.1
// @description Injecte le message, active le textarea et place le caret pour que tu n'aies qu'à appuyer sur Enter
// @match https://app.tandem.net/*
// @run-at document-end
// @grant none
// ==/UserScript==
(function () {
'use strict';
const MESSAGE_TEMPLATE = "Are you a kheyette, {name}?";
const MAX_WAIT_LOOPS = 60; // 60 * 500ms = 30s
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
function formatName(rawName) {
return rawName
.toLowerCase()
.split(/\s+/)
.filter(Boolean)
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
.join(' ');
}
function setReactValue(el, value) {
try {
const setter = Object.getOwnPropertyDescriptor(
window.HTMLTextAreaElement.prototype, 'value'
).set;
setter.call(el, value);
el.dispatchEvent(new Event('input', { bubbles: true }));
} catch (e) {
console.error('[TM DEBUG] setReactValue failed', e);
}
}
async function prepareForEnter() {
if (!/\/chats\//.test(location.href)) return;
console.log('[TM DEBUG] prepareForEnter on', location.href);
// attendre le textarea (ou un nouveau textarea si React le remplace)
let textarea = null;
for (let i = 0; i < MAX_WAIT_LOOPS; i++) {
textarea = document.querySelector('textarea');
if (textarea) break;
await sleep(500);
}
if (!textarea) {
console.warn('[TM DEBUG] textarea introuvable');
return;
}
// Si conversation déjà entamée -> ne rien faire
if (document.querySelector('.styles_timestamp__cjEoo')) {
console.log('[TM DEBUG] Conversation non-vierge détectée, script ignoré');
return;
}
// forceEnabled : retire disabled si React le remet
const forceEnabled = () => {
try {
if (textarea && textarea.disabled) {
textarea.removeAttribute('disabled');
textarea.disabled = false;
textarea.style.pointerEvents = 'auto';
textarea.style.opacity = '1';
// parfois React change l'élément ; on loggue pour debug
console.log('[TM DEBUG] forced enable textarea');
}
} catch (e) { /* ignore */ }
};
// Observers et interval pour maintenir le champ activé
forceEnabled();
const obs = new MutationObserver(forceEnabled);
obs.observe(textarea, { attributes: true, attributeFilter: ['disabled'] });
const intervalId = setInterval(forceEnabled, 500);
// Récupérer nom formaté
const h3 = document.querySelector('.styles_name__MFxv7 h3');
const rawName = h3 ? h3.textContent.trim() : 'there';
const formatted = formatName(rawName);
const message = MESSAGE_TEMPLATE.replace('{name}', formatted);
// Injecter le message si le champ est vide
if (!textarea.value) {
// attendre un peu pour être sûr que la réactivation est prise en compte
await sleep(300);
setReactValue(textarea, message);
console.log('[TM DEBUG] message injecté:', message);
} else {
console.log('[TM DEBUG] textarea déjà rempli, on skip l\'injection');
}
// Focus + click + placer le caret à la fin
try {
textarea.focus({ preventScroll: false });
// trigger a real-like click sequence
textarea.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
textarea.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
textarea.click();
// set caret to end
const len = textarea.value.length;
textarea.setSelectionRange(len, len);
textarea.scrollIntoView({ block: 'center', inline: 'nearest' });
// petit highlight visuel pour indiquer que tu dois appuyer Enter
const prevOutline = textarea.style.outline;
textarea.style.outline = '3px solid rgba(0,150,255,0.35)';
setTimeout(() => { textarea.style.outline = prevOutline; }, 2200);
console.log('[TM DEBUG] focus posé, caret à la fin -> appuie sur Enter pour envoyer');
} catch (e) {
console.warn('[TM DEBUG] focus/caret failed', e);
}
// cleanup si le textarea change (on arrête interval + observer)
const bodyObs = new MutationObserver(() => {
const current = document.querySelector('textarea');
if (current !== textarea) {
obs.disconnect();
bodyObs.disconnect();
clearInterval(intervalId);
console.log('[TM DEBUG] textarea remplacé, observers cleared');
}
});
bodyObs.observe(document.body, { childList: true, subtree: true });
}
function onUrlChange(cb) {
let oldHref = location.href;
new MutationObserver(() => {
if (location.href !== oldHref) {
oldHref = location.href;
cb();
}
}).observe(document.body, { childList: true, subtree: true });
}
// Exécution initiale + relance SPA
setTimeout(prepareForEnter, 1400);
onUrlChange(() => setTimeout(prepareForEnter, 1000));
})();Editor is loading...
Leave a Comment