angle-left

Модифицируем панель инструментов CKeditor в Liferay 7.x

Доброго времени суток.

Я уже писал несколько статей о работе с новыми плагинами в Liferay 7.x. Настало время очередной такой статьи.

При миграции некоторого портала с Liferay 6.2 на 7.x потребовалось перенести существующий плагин для CKEditor'а: он менял версию редактора, кастомизировал панель инструментов и добавлял некоторых новых возможностей (интеграция с SyntaxHighlighter и что-то ещё). Я уже писал статью о создании JSP hooks в лайфрее - казалось бы, просто можно было бы следовать описанной инструкции. Но в итоге я наткнулся на несколько подводных камней, о которых и хотел бы рассказать.

Первое, с чем пришлось столкнуться - это исчезновение привычного CKEditor'а. При установке Liferay 7.x "с нуля" повсюду обнаруживается непривычный редактор.

Прошу любить и жаловать, гордость разработчиков лайфрея - AlloyEditor!

К сожалению, мы стараний команды лайфрея не оценили и захотели вернуться к привычному редактору. Как оказалось, это делается очень легко - достаточно лишь отредактировать файл portal-ext.properties (или создать его в $LIFERAY_HOME, если его ещё нет) и добавить в него следующие строки

editor.wysiwyg.default=ckeditor
editor.wysiwyg.portal-impl.portlet.ddm.text_html.ftl=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.announcements.edit_entry.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.blogs.edit_entry.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.mail.edit.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.mail.edit_message.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.message_boards.edit_message.bb_code.jsp=ckeditor_bbcode
editor.wysiwyg.portal-web.docroot.html.portlet.message_boards.edit_message.html.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.taglib.ui.discussion.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.taglib.ui.email_notification_settings.jsp=ckeditor

после чего перезапустить портал.

По именам свойств несложно понять, за что они отвечают. Возможно, вам будет важно знать, что ckeditor - не единственный доступный редактор, у вас есть выбор из следующих значений свойств:

  • alloyeditor
  • alloyeditor_creole
  • ckeditor
  • ckeditor_bbcode
  • ckeditor_creole
  • simple
  • tinymce
  • tinymce_simple.

Итак, простыми манипуляциями мы вернули горячо любимый нами редактор. Что же дальше? А дальше нам хотелось бы немного подправить его под наши нужды: обновить версию, изменить порядок/номенклатуру кнопок в панели управления и так далее. Раньше это легко делалось с помощью хуков - можно было просто заменить существующие *.js файлы редактора прямо в портале. К сожалению, современные хуки (OSGI Fragments) не дают такой возможности - они позволяют заменить динамический контент (JSP-страницы) и добавить НОВЫЙ статический контент, но не дают возможности заменить старый. К счастью, мудрые разработчики лайфрея предусмотрели такую потребность и оставили "точку расширения" для конфигурации редактора. Как нынче водится, это очерденой OSGI-компонент.

Какова же будет последовательность действий? Для начала необходимо создать OSGI-плагин.

mvn archetype:generate -DarchetypeGroupId=com.liferay -DarchetypeArtifactId=com.liferay.project.templates.service -DliferayVersion=7.0 -DgroupId=test -DartifactId=ckeditot-config -Dversion=1.0 -Dpackage=test

И в проекте создать класс по образцу этого:

import com.liferay.portal.kernel.editor.configuration.BaseEditorConfigContributor;
import com.liferay.portal.kernel.editor.configuration.EditorConfigContributor;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.portlet.RequestBackedPortletURLFactory;
import com.liferay.portal.kernel.theme.ThemeDisplay;
import org.osgi.service.component.annotations.Component;

import java.util.Map;

@Component(
        immediate = true,
        property = {
                "editor.name=ckeditor",
                "editor.name=alloyeditor",
                "editor.config.key=contentEditor",
                "javax.portlet.name=com_liferay_blogs_web_portlet_BlogsPortlet",
                "javax.portlet.name=com_liferay_blogs_web_portlet_BlogsAdminPortlet",
                "service.ranking:Integer=1000000"
        },
        service = EditorConfigContributor.class
)
public class EditorConfig extends BaseEditorConfigContributor {

    @Override
    public void populateConfigJSONObject(JSONObject jsonObject, Map<String, Object> inputEditorTaglibAttributes, ThemeDisplay themeDisplay, RequestBackedPortletURLFactory requestBackedPortletURLFactory) {
        //YOUR EDITOR CONFIGURATION HERE
    }
}

Давайте разберём по частям здесь написанное.

Для начала необходимо показать, что данный класс является сервисом, который будет подключен к "точке расширения". Для создания OSGI-сервиса используется аннотация @Component с указанием реализуемого сервиса (service = EditorConfigContributor.class), а для упрощения реализации используется наследование от класса BaseEditorConfigContributor.

А к какому из множества редакторов будет применена данная конфигурация? Их в портале великое множество, как по типам (AlloyEditor, CKEditor...), так и по экземплярам - один и тот же редактор используется в разных портлетах, а порой и по нескольку раз в рамках одного. Чтобы указать, к каким именно редакторам будет применена конфигурация, необходимо задать несколько свойств в массиве property аннотации @Component

Во-первых, можно указать тип редактора (ckeditor, alloyeditor и прочие, упомянутые ранее) посредством свойства editor.name ("editor.name=ckeditor").

Во-вторых, можно указать портлет, в котором будет изменена конфигурация редактора - свойство javax.portlet.name ("javax.portlet.name=com_liferay_blogs_web_portlet_BlogsPortlet").

В-третьих, можно указать конкретный редактор в портлете, так как их может быть несколько. Так, в блогах на странице редактирования есть несколько редакторов:

<div class="cover-image-caption <%= (coverImageFileEntryId == 0) ? "invisible" : "" %>">
    <small>
        <liferay-ui:input-editor
                contents="<%= coverImageCaption %>"
                editorName="alloyeditor"
                name="coverImageCaptionEditor"
                placeholder="caption"
                showSource="<%= false %>"
        />
    </small>
</div>

<div class="col-md-8 col-md-offset-2">
    <div class="entry-title form-group">
        <h1>
            <liferay-ui:input-editor
                    contents="<%= HtmlUtil.escape(title) %>"
                    editorName="alloyeditor"
                    name="titleEditor"
                    placeholder="title"
                    showSource="<%= false %>"
            />
        </h1>
    </div>

    <aui:input name="title" type="hidden" />

    <div class="entry-subtitle">
        <h4><liferay-ui:input-editor contents="<%= HtmlUtil.escape(subtitle) %>" editorName="alloyeditor" name="subtitleEditor" placeholder="subtitle" showSource="<%= false %>" /> </h4>
    </div>

Обратите внимание на тэг liferay-ui:input-editor - это и есть редактор. Каждый из них идентифицируется атрибутом name (name="subtitleEditor", name="titleEditor" и т.д.). И в массиве property можно указать нужное вам имя через editor.config.key ("editor.config.key=contentEditor").

Наконец, можно повысить приоритет данного класса по отношению к другим с помощью свойства service.ranking ("service.ranking:Integer=1000000").

После указания конфигурируемых редакторов можно приступить к самому процессу настройки. Вся работа происходит в методе populateConfigJSONObject, в который первым аргументом передаётся JSON-объект, в котором вы можете указать требуемые вам параметры настройки. Тут уже обращайтесь к документации по редактору, чтобы узнать, как совершить требуемые вам изменения.

В качестве примера можно рассмотреть изменение стандартной панели инструментов. В объекте конфигурации есть несколько свойств, отвечающих за внешний вид панели в различных ситуациях:

  • toolbar_liferayArticle
  • toolbar_liferay
  • toolbar_simple
  • toolbar_editInPlace
  • toolbar_tablet
  • toolbar_email

А так будет выглядеть простейшая конфигурация для редактора в блогах:

JSONArray fst = JSONFactoryUtil.createJSONArray();
fst.put("Bold");
fst.put("Italic");
JSONArray snd = JSONFactoryUtil.createJSONArray();
snd.put("NumberedList");
snd.put("BulletedList");

JSONArray toolbars = JSONFactoryUtil.createJSONArray();
toolbars.put(fst);
toolbars.put("-");
toolbars.put(snd);

jsonObject.put("toolbar_liferay", toolbars);

Важно отметить, что в рамках портала существует множество "конфигураторов", и все они могут работать с одним и тем же JSON-объектом. Потому хорошей идеей будет повышение специфичности вашего сервиса - по возможности старайтесь указывать портлет, а порой даже и имя редактора. Я потратил немало времени, пытаясь изменить занчение свойства allowedContent на true (потребовалось для интеграции с SyntaxHighlighter) - в итоге проблема решилась, когда я указал в массиве свойств "editor.config.key=contentEditor".

На данном моменте позвольте откланяться. Краткое руководство по подключению плагинов к CKEditor я дам в следующих статьях этого уютного бложика.