Dodawanie własnych przycisków w edytorze TinyMCE 4.* – Część 2

Wpis ten stanowi rozwinięcie poprzedniego wpisu na temat dodawania własnych przycisków w edytorze TinyMCE po aktualizacji tego edytora jaka nastąpiła w WordPressie 3.9. Większość wpisu powstała na bazie zadawanych mi pytań – jeżeli pojawią się kolejne pytania to zapewne powstanie też trzecia część serii 😉

Spis treści

Kod źródłowy przykładów znajduje się na Githubie.

Dodawanie ikon w submenu

Ikony w submenu można dodać w bardzo prosty sposób – wystarczy zdefiniować atrybut icon w obiekcie tworzącym pozycję submenu:

(function() {
    tinymce.PluginManager.add('dziudek_tc_button2', function( editor, url ) {
        editor.addButton( 'dziudek_tc_button2', {
            title: 'My test button',
            type: 'menubutton',
            icon: 'icon dziudek-own-icon',
            menu: [
                {
                    text: 'Menu item I',
                    value: 'Text from menu item I',
                    icon: 'icon dashicons-wordpress',
                    onclick: function() {
                        editor.insertContent(this.value());
                    }
                },
                {
                    text: 'Menu item II',
                    value: 'Text from menu item II',
                    icon: 'icon dashicons-wordpress-alt',
                    onclick: function() {
                        editor.insertContent(this.value());
                    },
                    menu: [
                        {
                            text: 'First submenu item',
                            value: 'Text from sub sub menu',
                            icon: 'icon dashicons-smiley',
                            onclick: function(e) {
                                e.stopPropagation();
                                editor.insertContent(this.value());
                            }
                        },
                        {
                            text: 'Second submenu item',
                            value: 'Text from sub sub menu',
                            icon: 'icon dashicons-edit',
                            onclick: function(e) {
                                e.stopPropagation();
                                editor.insertContent(this.value());
                            }
                        }
                    ]
                },
                {
                    text: 'Menu item III',
                    value: 'Text from menu item III',
                    icon: 'icon dashicons-tickets',
                    onclick: function() {
                        editor.insertContent(this.value());
                    }
                }
           ]
        });
    });
})();

Efekt widoczny jest poniżej – ikonki zostały dodane do wszystkich pozycji submenu:

btn2_5

Wsparcie dla wielojęzyczności we wtyczkach dla TinyMCE

Teoretycznie pierwsze co się nasuwa przy tworzeniu skryptów JS ze wsparciem wielojęzyczności w WordPressie to wykorzystanie funkcji wp_localize_script. Niestety funkcja ta w wypadku wtyczek dla TinyMCE okaże się zupełnie bezużyteczna, ponieważ skrypty wtyczek TinyMCE nie są dodawane do kodu strony w standardowy sposób – są dodawane dynamicznie. Musimy zatem skorzystać z innego rozwiązania – filtru mce_external_languages. W tym celu tworzymy nową funkcję:

function dziudek_add_my_tc2_button_lang($locales) {
    $locales['dziudek_tc_button2'] = plugin_dir_path ( __FILE__ ) . 'translations.php';
    return $locales;
}

add_filter( 'mce_external_languages', 'dziudek_add_my_tc2_button_lang');

Jak widać, powyższa funkcja tworzy nowy element tablicy $locales, który wskazuje na plik z tłumaczeniem – translations.php umieszczonym w katalogu wtyczki dodającej przyciski.

Musimy zatem ów plik utworzyć:

<?php

if ( ! defined( 'ABSPATH' ) )
    exit;

if ( ! class_exists( '_WP_Editors' ) )
    require( ABSPATH . WPINC . '/class-wp-editor.php' );

function dziudek_tc_button_translation() {
    $strings = array(
        'button_label' => __('My test button I', 'dziudek_tc_button2'),
        'msg' => __('Hello World!!!!', 'dziudek_tc_button2')
    );

    $locale = _WP_Editors::$mce_locale;
    $translated = 'tinyMCE.addI18n("' . $locale . '.dziudek_tc_button", ' . json_encode( $strings ) . ");\n";

    return $translated;
}

$strings = dziudek_tc_button_translation();

Powyższy plik zawiera poza odpowiednimi zabezpieczeniami funkcję, która generuje fragment kodu JavaScript dla edytora TinyMCE – będzie to tablica asocjacyjna naszych tłumaczeń. Warto zwrócić uwagę na linijkę przypisującą wartość do zmiennej $translated – znajduje się w niej fragment dziudek_tc_button – to nazwa obiektu, który będzie przechowywał tłumaczenia. Odwołania do wartości w nim wykonywać będziemy w następujący sposób:

editor.getLang('OBIEKT.KLUCZ')

W przypadku skryptu dodającego przycisk musimy mieć dwie frazy, które da się przetłumaczyć – etykietę przycisku i tekst dodawany do edytora po kliknięciu przycisku:

(function() {
    tinymce.PluginManager.add('dziudek_tc_button1', function( editor, url ) {
        editor.addButton( 'dziudek_tc_button1', {
            title: editor.getLang('dziudek_tc_button.button_label'),
            icon: 'icon dziudek-own-icon',
            onclick: function() {
                editor.insertContent(editor.getLang('dziudek_tc_button.msg'));
            }
        });
    });
})();

Dodajemy drugi przycisk

Czasem może się okazać, że nie uda nam się upchnąć wszystkich potrzebnych funkcjonalności pod jednym przyciskiem – jego menu zrobi się za długie lub zbyt zagnieżdżone. Najprościej będzie po prostu dodać drugi przycisk z częścią funkcjonalności.

Musimy w tym celu zmodyfikować znane nam już z pierwszej części funkcje podpięte do filtrów mce_external_plugins oraz mce_buttons:

add_filter("mce_external_plugins", "dziudek_add_tinymce_plugin2");
add_filter('mce_buttons', 'dziudek_register_my_tc2_button');

Funkcja dodająca kod JS naszych przycisków będzie wyglądać następująco:

function dziudek_add_tinymce_plugin2($plugin_array) {
   	$plugin_array['dziudek_tc_button1'] = plugins_url( '/custom-icon-button.js', __FILE__ );
   	$plugin_array['dziudek_tc_button2'] = plugins_url( '/second-button.js', __FILE__ );
   	return $plugin_array;
}

Dodaliśmy po prostu w tablicy asocjacyjnej $plugin_array kolejną pozycję i zmieniliśmy nieco nazewnictwo – teraz każdy przycisk to nazwa wspólna zakończona numerem przycisku – to one posłużą nam do rozróżniania naszych przycisków. Same nazwy skryptów mogą być w zasadzie dowolne.

Pluginy mamy zatem dodane, pozostaje nam jeszcze dodać przyciski do listy przycisków w TinyMCE wykorzystując filtr mce_buttons:

function dziudek_register_my_tc2_button($buttons) {
   array_push($buttons, "dziudek_tc_button1");
   array_push($buttons, "dziudek_tc_button2");
   return $buttons;
}

Jak widać powyżej znów korzystamy ze znanych nam już nazw. Możemy ten schemat powielić teoretycznie dowolną ilość razy, choć należy pamiętać o jednym – nie należy przesadzać, gdyż kilka wtyczek dodających po kilka przycisków może skutecznie zaśmiecić edytor użytkownikowi. W miarę możliwości powinniśmy na swoje potrzeby tworzyć możliwie małą ilość przycisków, pamiętając o tym, że nie jesteśmy jedynymi autorami wtyczek korzystającymi z tej możliwości.

Pierwszy skrypt (custom-icon-button.js) w porównaniu do poprzedniej części ulega kosmetycznej zmianie – zmieniamy w nim nazwę przycisku na dziudek_tc_button1:

(function() {
    tinymce.PluginManager.add('dziudek_tc_button1', function( editor, url ) {
        editor.addButton( 'dziudek_tc_button1', {
            title: 'My test button I',
            icon: 'icon dziudek-own-icon',
            onclick: function() {
                editor.insertContent('Hello World I');
            }
        });
    });
})();

Drugi skrypt (second-button.js) także korzysta już z nowej nazwy:

function() {
    tinymce.PluginManager.add('dziudek_tc_button2', function( editor, url ) {
        editor.addButton( 'dziudek_tc_button2', {
            title: 'My test button II',
            icon: 'icon dashicons-wordpress',
            onclick: function() {
                editor.insertContent('Hello World II');
            }
        });
    });
})();

W edytorze powinny ukazać nam się dwa przyciski:

_2_1

Gdy zabraknie nam miejsca na przyciski

Przyciski, które dodawaliśmy pojawiały się zawsze w pierwszym widocznym pasku przycisku. Czasem jednak jest tam tłoczno – możemy na szczęście dodać nasze przyciski w drugim rzędzie – wystarczy zmienić nazwę filtra mce_buttons na: mce_buttons_2 jak poniżej:

add_filter('mce_buttons_2', 'dziudek_register_my_tc2_button');

btn_2_2

Mało tego – możemy nawet utworzyć kolejny rząd przycisków, który w czystej instalacji WordPressa nie istnieje – wystarczy wykorzystać filtr mce_buttons_3:

add_filter('mce_buttons_3', 'dziudek_register_my_tc2_button');

btn_2_3

Oczywiście od razu nasuwa się pytanie – ile takich wierszy z przyciskami można utworzyć? Niestety filtr mce_buttons_10 nie zadziała, ponieważ maksymalnie można utworzyć 4 rzędy przycisków w TinyMCE w WordPressie, zatem ostatni filtr to mce_buttons_4.

Umieszczając przyciski w rzędach innych niż pierwszy, należy pamiętać, że domyślnie pozostałe rzędy są ukryte, dopiero kliknięcie odpowiedniego przycisku rozwijającego pokaże pozostałe przyciski – dlatego naprawdę ważne przyciski lepiej umieścić w pierwszym rzędzie.

Usuwanie przycisków edytora

Usunięcie istniejących przycisków jest bardzo proste – wystarczy zamiast dodawać do tablicy przycisków element, usunąć go.

function dziudek_remove_tmce_btns($buttons) {
 unset($buttons[0]);
 return $buttons;
 }

 add_filter('mce_buttons', 'dziudek_remove_tmce_btns');

Przyciski indeksowane są w tablicy w takiej samej kolejności w jakiej są widoczne w edytorze. Zatem powyższy kod usunie nam przycisk dodający do tekstu efekt pogrubienia.

wp_editor() i tryb „teeny”

Może się zdarzyć, że używamy edytora TinyMCE w innym miejscu niż edytor wpisów – np. w komentarzach. Wtedy często edytor TinyMCE wywoływany jest w trybie uproszczonym zwanym teeny:

wp_editor( '', 'comment', array(
'teeny' => true,
 'quicktags' => false,
 'media_buttons' => true
 ));

Tryb ten charakteryzuje się uproszczoną strukturą edytora – ograniczoną do jednego wiersza przycisków.

Powoduje to też zmianę wykorzystywanych filtrów. Zamiast filtrów mce_buttons, mce_buttons_2, mce_buttons_3 oraz mce_buttons_4 mamy tylko jeden filtr – teeny_mce_buttons.

Dodatkowo możemy ograniczyć wczytywany w tym trybie pluginy korzystając z filtra teeny_mce_plugins.

Zatem gdybyśmy chcieli usunąć pierwszy przycisk z edytora korzystamy z następującego kodu:

function remove_teeny_mce_btns($buttons) {
 unset($buttons[0]);
 return $buttons;
 }

 add_filter('teeny_mce_buttons', 'remove_teeny_mce_btns');

Kilka przycisków w jednym – ButtonGroup

Teoretycznie trochę przekombinowaliśmy z definiowaniem oddzielnych skryptów dla dwóch przycisków – tak naprawdę możemy stworzyć dwa przyciski używając jednego rodzaju przycisku – ButtonGroup.

Ten rodzaj przycisków posiada właściwość items, która przyjmuje tablicę zawierającą obiekty opisujące przyciski:

(function() {
    tinymce.PluginManager.add('dziudek_tc_button2', function( editor, url ) {
        editor.addButton( 'dziudek_tc_button2', {
            title: 'My test button II',
            type: "buttongroup",
            items: [
           	        {
           	        	title: 'First button',
           	        	icon: 'icon dashicons-wordpress',
           	        	onclick: function() {
           	        	    editor.insertContent('Hello World I');
           	        	}
           	        },
           	        {
           	        	title: 'Second button',
           	        	type: 'menubutton',
           	        	icon: 'icon dashicons-wordpress-alt',
           	        	menu: [
	           	        	{
	           	        		text: "Item I"
	           	        	},
	           	        	{
	           	        		text: "Item II"
	           	        	}
           	        	]
           	        }
           	    ]
        });
    });
})();

Jak widać możemy zdefiniować dowolne przyciski korzystając z jednego skryptu. Ale to rozwiązanie ma pewien problem wizualny:

btn_2_4

Wbrew pozorom nie umieściłem przycisku grupującego w czwartym wierszu – znalazł się on na tej pozycji w wyniku problemów ze stylowaniem osobiście poprawiłem je poprzez następujący kod CSS dodany do pliku style.css:

.mce-toolbar .mce-container .mce-btn-group {
	display: inline-block;
}

Lista wyboru – Listbox

Alternatywą dla przycisku z menu (menubutton) jest lista wyboru – listbox. Charakterystyczne elementy listy wyboru to właściwości takie jak fixedWidth i values. Pierwsza właściwość odpowiada za ograniczenie wyświetlanej zawartości do określonej szerokości, natomiast druga właściwość zawiera elementy listy. Poniżej przykład listy wyboru dodającej do edytora listę wyboru generatorów tekstu typu lorem ipsum:

(function() {
    tinymce.PluginManager.add('dziudek_tc_button2', function( editor, url ) {
        editor.addButton( 'dziudek_tc_button2', {
            title: 'My test button II',
            type: "listbox",
            fixedWidth: true,
            text: 'Lorem ipsum',
            values: [{
            	text:'Lorem ipsum',
            	value:'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam dolor lacus, sodales ac massa nec, vulputate tempor libero. In in nunc ut odio ullamcorper venenatis id sed augue. Mauris eget sem aliquam, fermentum metus vitae, dapibus nibh. Ut lobortis egestas congue. In posuere velit vel nisl tincidunt, non mattis augue sagittis. Aenean mattis at enim ac facilisis. Sed dui eros, pretium eget sapien adipiscing, dictum molestie tortor. Aenean consequat accumsan est id vestibulum. Phasellus vulputate tellus ante, ac convallis erat sagittis et. Nulla id risus sed quam vestibulum blandit.',
                onclick: function() {
                    editor.insertContent(this.value());
                }
            },
            {
            	text:'Gangsta lorem ipsum',
                value: 'Lorizzle funky fresh dolor yippiyo amizzle, sheezy adipiscing elizzle. Nullam sapien away, funky fresh volutpizzle, bling bling i\'m in the shizzle, gravida vizzle, uhuh ... yih!. Shit shit fo shizzle. Sed erizzle. Own yo\' izzle dolor turpis tempizzle fo shizzle. Maurizzle black fo shizzle mah nizzle fo rizzle, mah home g-dizzle izzle gizzle. Crunk izzle tortizzle. Pellentesque bling bling uhuh ... yih! crackalackin. In hac break it down platea dictumst. Black daahng dawg. Curabitizzle yippiyo things, pretizzle black, mattis go to hizzle, eleifend vitae, nunc. Yo mamma suscipizzle. Integizzle semper velit go to hizzle.',
            	onclick: function() {
                    editor.insertContent(this.value());
                }
            },
            {
            	text:'Veggie ipsum',
            	value: 'Veggies es bonus vobis, proinde vos postulo essum magis kohlrabi welsh onion daikon amaranth tatsoi tomatillo melon azuki bean garlic. Gumbo beet greens corn soko endive gumbo gourd. Parsley shallot courgette tatsoi pea sprouts fava bean collard greens dandelion okra wakame tomato. Dandelion cucumber earthnut pea peanut soko zucchini.',
                onclick: function() {
                    editor.insertContent(this.value());
                }
            }]
        });
    });
})();

Jak widać zawartość listy elementów strukturalnie jest bardzo podobna do listy używanej w przycisku z submenu.

Efekt widoczny jest poniżej:

btn2_6