null

Liferay - интеграция SyntaxHighlighter в source-bundle портала

В качестве rich-text текстового редактора Liferay Portal использует FCKeditor, обеспечивающий широкие возможности по форматированию текста. Тем не менее, он обладает одним недостатком, существенным при написании текстов на программистские тематики - там отсутствует функционал по подсветке исходного кода. В общем случае, исходный код, написанный в этом редакторе и заключённый в теговую конструкцию вида <pre>code</pre> на странице портала выглядит так: 

public static void main(String[] args){
    System.out.println("Hello, world!");
}

Естественно, хотелось бы, чтобы код выглядел понагляднее - подсвечивался синтаксис, нумеровались строки, и т.д. К счастью, существует javascript-библиотека SyntaxHighlighter и плагин для FCKeditor, позволяющий добавить её функциональность в этот текстовый редактор. В этом случае, приведённый выше код будет выглядеть уже гораздо лучше:

public static void main(String[] args){
    System.out.println("Hello, world!");
}

Как это сделать? Достаточно подробная инструкция по интеграции SyntaxHighlighter вместе с этим плагином в war-архив портала доступна тут, но для наших задач она применима лишь частично, т.к. мы используем собственную сборку Liferay, и было бы неудобно после каждой пересборки повторно подключать плагины и модифицировать конфигурационные файлы. Гораздо проще в этом случае добавить плагин прямо в исходники портала и каждый раз после сборки получать полностью готовую к работе систему. Посмотрим, как это можно сделать на примере Liferay 5.2.3:

  1. Загружаем SyntaxHighlighter, распаковываем его и помещаем в директорию с js-библиотеками портала - $SOURCE_ROOT/portal-web/docroot/html/js/syntaxhighlighter
  2. Создаём конфигурационный файл customConfig.js и помещаем его в созданную на предыдущем шаге корневую директорию библиотеки. Содержимое файла приведено ниже:
    		/* This file should be loaded after all other SyntaxHighlighter JS files. */
    SyntaxHighlighter.config.clipboardSwf = '/html/js/syntaxhighlighter/scripts/clipboard.swf';
    SyntaxHighlighter.all();
  3. Добавляем ссылку на библиотеку в конфиги *всех* портлетов, в которых предполагается вывод отформатированных исходных кодов. В общем случае, это все портлеты, поддерживающие вывод форматрирванного текста - "Отображение сетевого контента" ("Web content display"), "Дневники" ("Blogs"), "Wiki" и "Отображение Wiki" ("Wiki display"). Конфиги всех интегрированных в Liferay Portal портлетов находятся в файле $SOURCE_ROOT/portal-web/docroot/WEB-INF/liferay-portlet.xml. Его нам и нужно подправить. Открываем файл, ищем конфиги каждого из нужных нам портлетов (hint - для версии 5.2.3 их ID - 33, 36, 54 и 56) и дописываем туда эти строчки:

    			<!-- syntax highlighter -->
    <header-portlet-css>/html/js/syntaxhighlighter/styles/shCore.css</header-portlet-css>
    <header-portlet-css>/html/js/syntaxhighlighter/styles/shThemeDefault.css</header-portlet-css>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shCore.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushAS3.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushCss.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushCSharp.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushCpp.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushDelphi.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushDiff.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushJava.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushJScript.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushPhp.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushPlain.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushPython.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushRuby.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushSql.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushVb.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushXml.js</header-portlet-javascript>
    <header-portlet-javascript>/html/js/syntaxhighlighter/customConfig.js</header-portlet-javascript>
    <!-- ... -->
    Для примера, после такой модификации конфиг блогового портлета будет выглядеть так:
    			<portlet>
        <portlet-name>33</portlet-name>
        <icon>/html/icons/blogs.png</icon>
        <struts-path>blogs</struts-path>
        <configuration-action-class>com.liferay.portlet.blogs.action.ConfigurationActionImpl</configuration-action-class>
        <indexer-class>com.liferay.portlet.blogs.util.Indexer</indexer-class>
        <open-search-class>com.liferay.portlet.blogs.util.BlogsOpenSearchImpl</open-search-class>
        <scheduler-class>com.liferay.portlet.blogs.job.BlogsScheduler</scheduler-class>
        <friendly-url-mapper-class>com.liferay.portlet.blogs.BlogsFriendlyURLMapper</friendly-url-mapper-class>
        <portlet-data-handler-class>com.liferay.portlet.blogs.lar.BlogsPortletDataHandlerImpl</portlet-data-handler-class>
        <social-activity-interpreter-class>com.liferay.portlet.blogs.social.BlogsActivityInterpreter</social-activity-interpreter-class>
        <control-panel-entry-category>content</control-panel-entry-category>
        <control-panel-entry-weight>7.0</control-panel-entry-weight>
        <preferences-unique-per-layout>false</preferences-unique-per-layout>
        <use-default-template>false</use-default-template>
        <restore-current-view>false</restore-current-view>
        <scopeable>true</scopeable>
        <private-request-attributes>false</private-request-attributes>
        <private-session-attributes>false</private-session-attributes>
        <render-weight>50</render-weight>
        <header-portlet-css>/html/portlet/blogs/css.jsp</header-portlet-css>    
        <!-- syntax highlighter -->
        <header-portlet-css>/html/js/syntaxhighlighter/styles/shCore.css</header-portlet-css>
        <header-portlet-css>/html/js/syntaxhighlighter/styles/shThemeDefault.css</header-portlet-css>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shCore.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushAS3.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushCss.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushCSharp.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushCpp.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushDelphi.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushDiff.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushJava.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushJScript.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushPhp.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushPlain.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushPython.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushRuby.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushSql.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushVb.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/scripts/shBrushXml.js</header-portlet-javascript>
        <header-portlet-javascript>/html/js/syntaxhighlighter/customConfig.js</header-portlet-javascript>
        <!-- ... -->
        <css-class-wrapper>portlet-blogs</css-class-wrapper>
        <add-default-resource>true</add-default-resource>
    </portlet>
    

С конфигурацией библиотеки мы закончили - теперь маркированный специальными тегами текст в портлетах будет подсвечиваться в соответствии с синтаксисом выбранного языка программирования. Но FCKeditor по-прежнему не умеет расставлять эти теги. Для того, чтобы "научить" его этому, нужно интегрировать в него SyntaxHighlighter-плагин и добавить кнопку этого плагина на тулбар редактора. Посмотрим, как это можно сделать:

  1. Загружаем плагин, распаковываем его и помещаем в директорию $SOURCE_ROOT/portal-web/docroot/html/js/editor/_fckeditor/editor/plugins (!). Обратите внимание, что в исходниках сам FCKeditor лежит в заархивированном виде в каталоге $SOURCE_ROOT/portal-web/third-party, а его распаковкой и добавлением в javascript-библиотеки портала занимается ant во время процесса сборки. Если мы откроем файл build.xml в каталоге $SOURCE_ROOT, то увидим там следующее:

    		<target name="build-fckeditor">
    ...
        <delete dir="docroot/html/js/editor/fckeditor" />
    ...
        <copy todir="docroot/html/js/editor/fckeditor" overwrite="no">
        <fileset dir="docroot/html/js/editor/_fckeditor" />
    </target>

    Таким образом при каждой пересборке каталог редактора пересоздаётся заново, а все специфичные для Liferay файлы хранятся в каталоге $SOURCE_ROOT/portal-web/docroot/html/js/editor/_fckeditor. Поэтому копировать плагин и конфигурационные файлы для SyntaxHighlighter надо именно туда.

  2.  Открываем файл $SOURCE_ROOT/portal-web/docroot/html/js/editor/_fckeditor/fckconfig.jsp и добавляем на тулбар для режимов FCKeditor "liferay" и "liferay-article" иконку форматирования исходников:
    			FCKConfig.ToolbarSets["liferay"] = [
        ['Style','FontSize','-','TextColor','BGColor'],
        ['Bold','Italic','Underline','StrikeThrough'],
        ['Subscript','Superscript'],
        '/',
        ['Undo','Redo','-','Cut','Copy','Paste','PasteText','PasteWord','-','SelectAll','RemoveFormat'],
        ['Find','Replace','SpellCheck'],
        ['OrderedList','UnorderedList','-','Outdent','Indent'],
        ['JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],
        '/',
        ['Source'],
        ['Link','Unlink','Anchor'],
        ['Image','Flash','Table','-','Smiley','SpecialChar','-','SyntaxHighLight2']
    ] ;
    
    FCKConfig.ToolbarSets["liferay-article"] = [
        ['Style','FontSize','-','TextColor','BGColor'],
        ['Bold','Italic','Underline','StrikeThrough'],
        ['Subscript','Superscript'],
        '/',
        ['Undo','Redo','-','Cut','Copy','Paste','PasteText','PasteWord','-','SelectAll','RemoveFormat'],
        ['Find','Replace','SpellCheck'],
        ['OrderedList','UnorderedList','-','Outdent','Indent'],
        ['JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],
        '/',
        ['Source'],
        ['Link','Unlink','Anchor'],
        ['Image','Flash','Table','-','Smiley','SpecialChar','LiferayPageBreak','-','SyntaxHighLight2']
    ] ;

Всё! Теперь можно пересобирать Liferay (подробная инструкция есть в моём предыдущем посте), разворачивать его на сервере и пользоваться новым функционалом. В тулбаре FCKeditor появится одна новая кнопка:

Если кликнуть по ней, то мы увидим всплывающее окно с формой для ввода исходного кода и селектор со списком языков программирования, синтаксис которых умеет подсвечивать SyntaxHighlighter:

Коротко о себе:

Работаю ведущим программистом в компании Tune IT и ассистентом кафедры Вычислительной техники в Университете ИТМО .

Занимаюсь проектами, связанными с разработкой разного рода веб-приложений (порталы, CRM-системы, системы электронного документооборота), а также, в рамках научной работы на кафедре, изучаю возможности применения семантического анализа в задачах САПР.