templates/editor: variavel mobile insere na posicao do cursor

Antes: variavel inseria sempre no fim do texto no mobile.
Causa: usavamos append direto no form (form[field] += tag) porque
o foco estava no drawer e Jodit.insertHTML travava.

Fix: capturar selection ANTES do drawer abrir, restaurar antes de
inserir.

JoditEmailEditor expose API estendida:
- saveSelection() -> retorna markers (jodit.selection.save())
- restoreSelection(markers) -> re-foca editor + restaura markers
- focus() -> foca o editor

DocumentTemplateEditor:
- ref savedSelection capturada em openDrawer('vars'): snapshot dos
  markers do Jodit no momento (cursor original)
- insertVariable mobile: setTimeout 280ms apos fechar drawer ->
  restaura markers -> insertHTML (cursor volta pra onde estava ->
  variavel aparece no ponto exato)
- Fallback append no form se restore falhar
- savedSelection limpa em fecharDrawer + apos insert

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-05-21 17:47:38 -03:00
parent 87a1ac1358
commit 79425a3c9a
2 changed files with 71 additions and 21 deletions
+18 -1
View File
@@ -194,7 +194,24 @@ watch(
// ── API exposta ─────────────────────────────────────────────── // ── API exposta ───────────────────────────────────────────────
defineExpose({ defineExpose({
insertHTML: (html) => jodit?.selection.insertHTML(html) insertHTML: (html) => jodit?.selection.insertHTML(html),
// Salva markers da seleção atual antes do foco sair do editor
// (ex: usuário abre drawer e perde o cursor). Retorna o array de
// markers que pode ser passado pra restoreSelection depois.
saveSelection: () => {
if (!jodit) return null;
try { return jodit.selection.save(); }
catch { return null; }
},
// Restaura selection a partir dos markers salvos. Re-foca o editor.
restoreSelection: (markers) => {
if (!jodit) return;
try {
jodit.focus();
if (markers) jodit.selection.restore(markers);
} catch { /* silencioso */ }
},
focus: () => jodit?.focus()
}); });
</script> </script>
@@ -66,31 +66,43 @@ const editorRodape = ref(null)
function insertVariable(varKey) { function insertVariable(varKey) {
const tag = `{{${varKey}}}` const tag = `{{${varKey}}}`
// No mobile: 1) fecha drawer (imediato); 2) deferir append do form e
// re-render do Jodit pra DEPOIS da transição (250ms). Modificar o form
// durante a transição causa concorrência entre repaint do drawer
// saindo + Jodit re-rendering com novo HTML → trava.
if (isMobile.value) {
drawerOpen.value = false;
const field = cursorField.value;
// setTimeout > duração da transição CSS (.dte-mobile-drawer = 250ms)
setTimeout(() => {
form.value[field] = (form.value[field] || '') + tag;
if (!form.value.variaveis.includes(varKey)) {
form.value.variaveis = [...form.value.variaveis, varKey];
}
}, 280);
return;
}
// Desktop: insertHTML mantém posição do cursor
const editorMap = { const editorMap = {
cabecalho_html: editorCabecalho, cabecalho_html: editorCabecalho,
corpo_html: editorCorpo, corpo_html: editorCorpo,
rodape_html: editorRodape rodape_html: editorRodape
} }
const editorRef = editorMap[cursorField.value] const editorRef = editorMap[cursorField.value]
// No mobile: fecha drawer + defere insertHTML pós-transição.
// Restaura a selection capturada quando o drawer abriu (cursor
// original do usuário) antes de inserir → variável aparece no
// ponto certo do texto, não no final.
if (isMobile.value) {
drawerOpen.value = false;
const markers = savedSelection.value;
setTimeout(() => {
try {
if (markers && editorRef?.value?.restoreSelection) {
editorRef.value.restoreSelection(markers);
}
if (editorRef?.value?.insertHTML) {
editorRef.value.insertHTML(tag);
} else {
// Fallback se a API expose falhar
form.value[cursorField.value] = (form.value[cursorField.value] || '') + tag;
}
} catch {
form.value[cursorField.value] = (form.value[cursorField.value] || '') + tag;
}
if (!form.value.variaveis.includes(varKey)) {
form.value.variaveis = [...form.value.variaveis, varKey];
}
savedSelection.value = null;
}, 280);
return;
}
// Desktop: insertHTML mantém posição do cursor (foco já tá no editor)
if (editorRef?.value?.insertHTML) { if (editorRef?.value?.insertHTML) {
editorRef.value.insertHTML(tag) editorRef.value.insertHTML(tag)
} else { } else {
@@ -119,11 +131,32 @@ function _onMqMobileChange(e) {
isMobile.value = e.matches; isMobile.value = e.matches;
if (!e.matches) drawerOpen.value = false; if (!e.matches) drawerOpen.value = false;
} }
// Selection salva do editor ativo no momento de abrir o drawer de
// variáveis. Permite inserir na posição original do cursor mesmo
// depois do user navegar pelo drawer/perder foco.
const savedSelection = ref(null);
function openDrawer(tab) { function openDrawer(tab) {
drawerTab.value = tab || 'form'; drawerTab.value = tab || 'form';
// Quando abre "Variáveis", salva selection do editor ativo agora
// (cursor original do usuário) pra restaurar depois da inserção.
if (tab === 'vars') {
const editorMap = {
cabecalho_html: editorCabecalho,
corpo_html: editorCorpo,
rodape_html: editorRodape
};
const editorRef = editorMap[cursorField.value];
savedSelection.value = editorRef?.value?.saveSelection?.() || null;
} else {
savedSelection.value = null;
}
drawerOpen.value = true; drawerOpen.value = true;
} }
function fecharDrawer() { drawerOpen.value = false; } function fecharDrawer() {
drawerOpen.value = false;
savedSelection.value = null;
}
onMounted(() => { onMounted(() => {
if (typeof window !== 'undefined' && window.matchMedia) { if (typeof window !== 'undefined' && window.matchMedia) {