De fiecare dată când adăugam într-un articol o secvență de cod în rând cu textul – cum Tuz–Guttenberg mi se pare o pecingine supraturată, folosesc Classic Editor – EnlighterJS îmi adaugă de fiecare datp două linii de prisos. Problema s-a dovedit a fi de la faptul că, deși își construiește elementul HTML părinte în funcție de mod (code pentru inline, pre altminteri), adaugă otova un paragraf gol la final:
editor.insertContent(
sp +
'<' + tag +
' class="EnlighterJSRAW" data-enlighter-language="' +
e.data.lang + '">' +
code +
'</' + tag + '>' +
sp +
'<p></p>'
);
Cea mai la îndemână soluție a fost un mic modul pentru TinyMCE unde ascult evenimentul BeforeSetContent și, dacă detectez un conținut asemănător cu o secvență de cod integrat în text (<code>*</code>), înlătur acel paragraf gratuit.
Firește că, într-un acces de mușchii mei minte nu are, ori poate un efect secundar de la anii buni lucrați în medii precum Internet Explorer 6 (pentru cei tineri, nu doar că nu aveam AI, nu aveam nici măcar jQuery și ne măsuram sculele după numărul de navigatoare-n care reușeam să ne facem să ruleze librăria AJAX), era cât pe ce să înlocuiesc metoda editor.insertContent cu totul.
Înregistrarea modulului se face folosind procedura standard pentru Classic Editor, măcar că nu e practic decât un fișier încărcat „lateral”. Trebuie avută, din acest motiv, băgare de seamă la inițializare, întrucât „plug-in-ul” poate ajunge să fie inițializat într-un punct când editorul este deja instanțiat, deci aș fi riscat să primesc evenimentul AddEditor mai din părți și mai pe seară. Așadar, din precauție, am verificat ce alte editoare sunt pe pagină și am intrat fără să mai bat la ușă. Ce? Cum? A, eu? Eram deja aici:
function initialize() {
if (!window.tinymce) {
console.warn('No TinyMCE instance found!');
return;
}
// Watch out for other editors
tinymce.on('AddEditor', function (event) {
attachEnlighterInlineFix(event.editor);
});
// Bind to current ones
tinymce.each(tinymce.editors || [], function (editor) {
attachEnlighterInlineFix(editor);
});
}
Iar secvența de cod mai de interes, unde se procesează textul propriu-zis, de o manieră cât mai selectivă cu putință (adică țintesc anume paragrafele goale: ce nu conțin nimic ori conțin doar spații goale cât să dea la kilogram):
function matchesInlineCodeInsert(content) {
return /<code\b[^>]*class=["'][^"']*\bEnlighterJSRAW\b/i
.test(content);
}
function matchesBlockCodeInsert(content) {
return /<pre\b[^>]*class=["'][^"']*\bEnlighterJSRAW\b/i
.test(content);
}
function attachEnlighterInlineFix(editor) {
if (!editor || editor.__enlighterInlineFixAttached) {
return;
}
//Marker to avoid doubling down on events
editor.__enlighterInlineFixAttached = true;
editor.on('BeforeSetContent', function (event) {
if (event.selection !== true
|| typeof event.content !== 'string') {
return;
}
//We are trying to be as selective as possible
if (matchesInlineCodeInsert(event.content)) {
event.content = event.content.replace(
/<p>\s*<\/p>\s*$/i,
''
);
} else if (matchesBlockCodeInsert(event.content)) {
event.content = event.content.replace(
/<p>\s*<\/p>\s*$/i,
'<p>...</p>'
);
}
});
}
Am adăugat în plus și o mică ajustare pentru cazul în care este un bloc de cod normal, astfel încât să nu-mi adauge un paragraf gol, ci unul cu trei puncte în el că poate nu mi-e mereu clar de unde trebuie să continui. Fiecare cu păsărelele lui.
–
Că tot eram în zonă, mă tracasa și faptul că nu pot șterge un bloc de cod dintr-un singur gest, un click sau ceva. Rezolvai tot cu un modul pentru TinyMCE, dar definit formal. Cerințele de căpătâi au fost, pe scurt:
– să am un buton în bara editorului clasic;
– să fie activat doar când sunt în cadrul unui bloc de cod;
– să am și o scurtătură din tastatură;
– să-l pot învia din morți folosind Undo;
– cursorul să rămâie ancorat la nodul următor sau la finalul documentului dacă blocul de cod este ultimul element.
Pentru activarea, respectiv dezactivarea butonului am folosit evenimentul NodeChange. Dacă am plasat cursorul în cadrul unei ierarhii ce conține un bloc de cod EnlighterJS, îl activez. Altfel, rămâne sau merge înapoi pe bancă:
function maybeEnlighterBlock(node) {
return node.nodeType === TARGET_NODE_TYPE_ID &&
node.nodeName.toUpperCase() === 'PRE' &&
editor.dom.hasClass(
node,
'EnlighterJSRAW'
);
}
function findEnlighterBlock() {
var node = editor.selection.getNode();
var body = editor.getBody();
while (node && node !== body) {
if (maybeEnlighterBlock(node)) {
return node;
}
node = node.parentNode;
}
return null;
}
//...
editor.on('NodeChange', function (event) {
if (!deleteButton) {
return;
}
deleteButton.disabled(
!findEnlighterBlock()
);
});
Ștergerea propriu-zisă este gândită ca o comandă, executată din două locuri: prin scurtătura Ctrl+Shift+e și prin click pe buton. Evident, orice altă parte interesată poate să o refolosească similar:
editor.addCommand(
'RemoveEnlighterBlock',
function () {
removeEnlighterBlock(
findEnlighterBlock()
);
}
);
editor.addShortcut(
'ctrl+shift+e',
'Delete code block',
'RemoveEnlighterBlock'
);
editor.addButton('EnlighterDelete', {
text: 'Delete code',
title: 'Delete the selected EnlighterJS code block',
icon: 'remove',
disabled: true,
onPostRender: function () {
deleteButton = this;
},
onclick: function () {
editor.execCommand('RemoveEnlighterBlock');
}
});
Pentru a putea beneficia de funcția de Undo, operațiunea trebuie înregistrată în istoricul TinyMCE cu ajutorul editor.undoManager.transact(). În rest, nimic spectaculos (data-mce-bogus este un atribut adăugat de librărie pentru a gestiona corect noduri auxiliare ce nu trebuie tratate ca parte normală a conținutului):
function isEditableNode(node) {
return node.nodeType === TARGET_NODE_TYPE_ID &&
node.getAttribute('data-mce-bogus') !== '1' &&
node.getAttribute('contenteditable') !== 'false';
}
function findNextEditableNode(block) {
var node = block.nextSibling;
while (node) {
if (isEditableNode(node)) {
return node;
}
node = node.nextSibling;
}
return null;
}
function removeEnlighterBlock(block) {
if (!block || !block.parentNode) {
return;
}
editor.undoManager.transact(function () {
var caretTarget = findNextEditableNode(block);
//If this is the last element in the document,
// create a paragraph to host the cursor
if (!caretTarget) {
//And add a non breaking space for good measure
caretTarget = editor.dom.create('p', {}, '\u00a0');
editor.dom.insertAfter(
caretTarget,
block
);
}
editor.dom.remove(block);
editor.selection.setCursorLocation(caretTarget, 0);
editor.setDirty(true);
});
editor.nodeChanged();
editor.focus();
}
După tot efortul am constatat că de fapt EnlighterJS avea o funcție de ștergere, fără a fi preluat în modulul lor de integrare cu WordPress și clasele CSS aferente butonului, deci nu apărea nicăieri. Ca să nu fi luat bătaie degeaba, am păstrat și butonul meu și pe-al lor, după ce am adăugat clasele în tema mea:
#EnlighterToolbar .deleteicon {
display: block;
width: 32px;
height: 32px;
position: absolute;
cursor: pointer;
right: 15px;
border-radius: 0px;
color: #ffffff;
font-weight: bold;
text-align: center;
top: 57px !important;
background-image: url(./img/enlighter-code-delete-32.png) !important;
}
Și arată așa (am modificat până la urmă și alte proprietăți):

EnlighterJS – stiluri CSS modificate
