✨ feat(Formules): Ajoute champ 'pos' pour ordonner les formules. 🐛 fix(CrmEditor): Améliore la gestion du copier-coller et retire le toast d'erreur. ```
136 lines
7.1 KiB
JavaScript
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 }));
|
|
}
|
|
}
|