Files
ludikevent_crm/assets/libs/CrmEditor.js
Serreau Jovann b6f90721ad ```
 feat(Formules): Ajoute champ 'pos' pour ordonner les formules.
🐛 fix(CrmEditor): Améliore la gestion du copier-coller et retire le toast d'erreur.
```
2026-01-30 09:48:27 +01:00

136 lines
7.1 KiB
JavaScript

export class CrmEditor extends HTMLTextAreaElement {
connectedCallback() {
this.style.display = 'none';
this.editorContainer = document.createElement('div');
this.editorContainer.className = "w-full rounded-2xl overflow-hidden ring-1 ring-white/20 bg-slate-950/40 backdrop-blur-2xl shadow-2xl transition-all duration-300 focus-within:ring-blue-500/50 my-4 relative";
this.editorContainer.innerHTML = `
<div class="flex items-center gap-1.5 p-3 border-b border-white/10 bg-black/40">
<button type="button" data-cmd="undo" class="w-9 h-9 flex items-center justify-center hover:bg-white/10 rounded-lg transition-all text-white active:scale-90">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" /></svg>
</button>
<div class="w-px h-5 bg-white/10 mx-1"></div>
<button type="button" data-cmd="bold" id="btn-bold" class="w-9 h-9 flex items-center justify-center rounded-lg transition-all text-white font-bold hover:bg-white/10">B</button>
<button type="button" data-cmd="underline" id="btn-underline" class="w-9 h-9 flex items-center justify-center rounded-lg transition-all text-white underline hover:bg-white/10">U</button>
<div class="relative w-9 h-9 flex items-center justify-center hover:bg-white/10 rounded-lg transition-all">
<input type="color" id="text-color-input" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer">
<div id="color-preview" class="flex flex-col items-center">
<span class="text-white font-bold text-xs">A</span>
<div class="w-4 h-0.5 bg-red-500 mt-0.5 rounded-full shadow-sm" id="color-bar"></div>
</div>
</div>
<div class="w-px h-5 bg-white/10 mx-1"></div>
<button type="button" data-cmd="justifyLeft" id="btn-left" class="w-9 h-9 flex items-center justify-center rounded-lg transition-all text-white hover:bg-white/10">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path d="M4 6h16M4 12h10M4 18h16" /></svg>
</button>
<button type="button" data-cmd="justifyCenter" id="btn-center" class="w-9 h-9 flex items-center justify-center rounded-lg transition-all text-white hover:bg-white/10">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path d="M4 6h16M7 12h10M4 18h16" /></svg>
</button>
<button type="button" data-cmd="justifyRight" id="btn-right" class="w-9 h-9 flex items-center justify-center rounded-lg transition-all text-white hover:bg-white/10">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path d="M4 6h16M10 12h10M4 18h16" /></svg>
</button>
</div>
<div id="wysiwyg-area"
contenteditable="true"
class="p-6 min-h-[300px] max-h-[600px] overflow-y-auto focus:outline-none bg-white text-slate-900 prose prose-slate max-w-none shadow-inner break-words w-full">
${this.value || '<p><br></p>'}
</div>
<div class="flex items-center justify-between px-4 py-2 border-t border-white/10 bg-black/20 text-[11px] font-medium tracking-wide">
<div id="char-counter" class="flex items-center gap-2">
<span id="char-count" class="text-white bg-white/10 px-2 py-0.5 rounded-full">0 caractères</span>
<span id="seo-warning" class="text-orange-400 opacity-0 transition-opacity italic">Texte insuffisant</span>
</div>
<div class="text-white/40 uppercase tracking-widest font-bold">EsyWysiwyg 1.1</div>
</div>
`;
this.insertAdjacentElement('afterend', this.editorContainer);
this.wysiwyg = this.editorContainer.querySelector('#wysiwyg-area');
this.colorInput = this.editorContainer.querySelector('#text-color-input');
this.colorBar = this.editorContainer.querySelector('#color-bar');
this.initEvents();
this.updateStats();
}
initEvents() {
// --- NOUVELLE LOGIQUE DE NETTOYAGE (PASTE) ---
this.wysiwyg.addEventListener('paste', (e) => {
e.preventDefault();
// On récupère uniquement le texte brut du presse-papier
const text = (e.originalEvent || e).clipboardData.getData('text/plain');
// On l'insère proprement à l'endroit du curseur sans aucune balise
document.execCommand('insertText', false, text);
this.syncValue();
this.updateStats();
});
// Boutons standards
const buttons = this.editorContainer.querySelectorAll('button[data-cmd]');
buttons.forEach(btn => {
btn.onmousedown = (e) => {
e.preventDefault();
document.execCommand(btn.getAttribute('data-cmd'), false, null);
this.syncValue();
this.checkActiveStates();
};
});
// Gestion de la couleur
this.colorInput.oninput = (e) => {
const color = e.target.value;
this.colorBar.style.backgroundColor = color;
document.execCommand('foreColor', false, color);
this.syncValue();
};
this.wysiwyg.addEventListener('keyup', () => {
this.checkActiveStates();
this.updateStats();
});
this.wysiwyg.addEventListener('input', () => {
this.syncValue();
this.updateStats();
});
}
updateStats() {
const text = this.wysiwyg.innerText || "";
const count = text.trim().length;
this.editorContainer.querySelector('#char-count').innerText = `${count} caractère${count > 1 ? 's' : ''}`;
const warning = this.editorContainer.querySelector('#seo-warning');
(count > 0 && count <= 100) ? warning.classList.remove('opacity-0') : warning.classList.add('opacity-0');
}
checkActiveStates() {
const activeStyles = ['bg-white/20', 'ring-1', 'ring-white/30'];
const commands = [
{ id: 'bold', btn: '#btn-bold' },
{ id: 'underline', btn: '#btn-underline' },
{ id: 'justifyLeft', btn: '#btn-left' },
{ id: 'justifyCenter', btn: '#btn-center' },
{ id: 'justifyRight', btn: '#btn-right' }
];
commands.forEach(c => {
const el = this.editorContainer.querySelector(c.btn);
if (el) {
document.queryCommandState(c.id) ? el.classList.add(...activeStyles) : el.classList.remove(...activeStyles);
}
});
}
syncValue() {
this.value = this.wysiwyg.innerHTML;
this.dispatchEvent(new Event('input', { bubbles: true }));
this.dispatchEvent(new Event('change', { bubbles: true }));
}
}