Настройка редактора Rich Text в JIRA

Уровень опыта

продвинутый

Временная оценка

0:45

Применимость Atlassian

JIRA 7.3+

 

Обзор функций

Если вы закончили учебник Написание плагина для редактора Rich Text в JIRA, вы узнаете, как добавить новые функции в редактор Rich Text. После прочтения этого руководства вы узнаете, как добавить макрос, который использует новый пользовательский элемент. Предположим, мы хотим создать простой макрос {info}. Конечный результат:

РИСУНОК

Давайте погрузимся в детали.

Представленный пример имеет следующую структуру кода:

РИСУНОК

Создание макроса информации

Основными компонентами нашего макроса являются:

  • InfoMacro - класс рендеринга,
  • шаблоны soy- разметка HTML и Wiki,
  • CSS - внешний вид,
  • тесты - чтобы убедиться, что все работает так, как вы ожидаете,
  • i18n - копия, текст которой отображается пользователю.

Эти части описаны в учебнике - Написание плагина для Rich Text Editor в JIRA.

Добавление класса рендера InfoMacro -

Листинг InfoMacro.java


package com.atlassian.jira.plugin.editor.ref;

import com.atlassian.jira.template.soy.SoyTemplateRendererProvider;
import com.atlassian.renderer.RenderContext;
import com.atlassian.renderer.v2.RenderMode;
import com.atlassian.renderer.v2.macro.BaseMacro;
import com.atlassian.renderer.v2.macro.MacroException;
import com.atlassian.soy.renderer.SoyException;
import com.atlassian.soy.renderer.SoyTemplateRenderer;
import com.google.common.collect.ImmutableMap;

import java.util.Map;

public class InfoMacro extends BaseMacro {

    private static final String FALLBACK_RENDER_OUTPUT = "{info}%s{info}";

    private final SoyTemplateRenderer soyTemplateRenderer;

    public InfoMacro(final SoyTemplateRendererProvider soyTemplateRendererProvider) {
        super();
        this.soyTemplateRenderer = soyTemplateRendererProvider.getRenderer();
    }

    @Override
    public boolean hasBody() {
        return true;
    }

    @Override
    public RenderMode getBodyRenderMode() {
        return RenderMode.allow(RenderMode.F_ALL);
    }

    @Override
    public String execute(Map parameters, String body, RenderContext renderContext) throws MacroException {

        ImmutableMap.Builder templateParams = ImmutableMap.builder();
        templateParams.put("content", body);

        try {
            return this.soyTemplateRenderer.render(
                    "com.atlassian.jira.plugins.jira-editor-ref-plugin:handler",
                    "RefPlugin.Macros.Info.html",
                    templateParams.build());
        } catch (SoyException e) {
            return String.format(FALLBACK_RENDER_OUTPUT, body);
        }
    }
}
,>,>

Добавление шаблонов soy - разметка HTML и Wiki

Листинг info-macro.soy


 
{namespace RefPlugin.Macros.Info}
/**
 * @param content
 */
{template .html}
    <info-macro>{$content|noAutoescape}</info-macro>
{/template}

/**
 * @param content
 */
{template .wiki}
    {lb}info{rb}{$content}{lb}info{rb}
{/template}

/**
 * @param innerMarkup
 */
{template .convert}
    {lb}info{rb}{$innerMarkup}{lb}info{rb}
{/template} 

Обратите внимание, что внутри html-шаблона используется модификатор noAutoescape, потому что мы хотим облегчить установку истинного HTML, например тегов p или strong.

Добавление CSS - внешний вид

Листинг info-macro.less


info-macro {
  @text-padding: 18px;
  @icon-indent: -(@text-padding - 3px);

  margin: @text-padding/4 0;
  padding: 10px 4px 4px 4px;
  display: block;
  background: #3572b0;

  > * {
    padding: 0 @text-padding @text-padding/4 @text-padding !important;
    background: #fff !important;
    margin: 0 !important;
  }

  > *:first-child:before {
    content: "\2139";
    font-size: 22px;
    text-indent: 0;
    margin-left: @icon-indent;
    color: inherit;
    font-weight: 400;
    -webkit-font-smoothing: antialiased;
    font-style: normal;
    speak: none;
  }
}

Добавление тестов

Этот раздел содержит только пример нескольких простых модульных тестов.

Для получения дополнительной информации см. Раздел * Запуск и тестирование * раздела * Учебник - Написание плагина для Rich Text Editor в JIRA.

Листинг info-macro-init.js


AJS.test.require(['com.atlassian.jira.plugins.jira-editor-ref-plugin:handler'], function () {
    var htmlConverter = require('jira/editor/converter');

    module('InfoMacro handler');

    test('Should convert HTML to wiki markup for info macro properly', function () {
        assertConversion('<info-macro><p>WIP</p></info-macro>', '{info}WIP{info}');
        assertConversion('<info-macro><p>WIP<br>new line</p></info-macro>', '{info}WIP\nnew line{info}');
        assertConversion('<info-macro><p>one two</p><ul><li>a</li><li>b</li></ul></info-macro>', '{info}one two\n * a\n * b{info}');
    });

    var assertConversion = function (html, markup, testName) {
        htmlConverter.convert(html).then(function (result) {
            equal(result, markup, testName);
        }).fail(function (e) {
            throw e;
        });
    };
});

Добавление i18n - копия

Листинг ref-i18n.properties


refplugin.toolbar.info=Info
refplugin.macro.info.placeholder=Info...

Разрешение использования пользовательского элемента внутри редактора

Мы используем новый пользовательский элемент info-macro, и TinyMCE ничего не знает об этом теге, поэтому нам нужно сказать TinyMCE, как поддерживать тег.Для этого нам нужен JS-файл, который будет обрабатывать инициализацию * info * macro. Назовем его info-macro-init.js.

Листинг info-macro-init.js


require([
    "jira/editor/customizer"
], function (
    Customizer
) {
    Customizer.customizeSettings(function (tinymceSettings, tinymce, SchemaBuilder) {
        SchemaBuilder.withCustomElement('info-macro', ['p', 'ul', 'ol']);
    });
}); 

  • Мы вызываем require, чтобы иметь возможность использовать Customizer(Настройщик), поскольку он позволяет нам добавлять
  • Данная функция обратного вызова function (tinymceSettings, tinymce, SchemaBuilder) {...} вызывается до инициализации экземпляра редактора TinyMCE.

Есть три параметра, которые мы можем использовать:

  • объект tinymceSettings, который используется для инициализации экземпляра редактора TinyMCE: tinymce.init (tinymceSettings);(подробнее см. документацию TinyMCE)tinymce
  • Основной объект tinymce TinyMCE, который мы можем использовать, например, для добавления нового плагина TinyMCE(некоторые примеры приведены в этой части документации TinyMCE)
  • Редактор Rich Text Editor SchemaBuilder контролирует параметры схемы, связанные с TinyMCE, такие как schema, valid_elements, valid_children или custom_elements, потому что только подмножество HTML поддерживается форматом Wiki Markup, который используется в качестве формата хранения в JIRA.
  • SchemaBuilder используется для добавления пользовательского элемента info-macro вместе с разрешенными дочерними элементами: p, ul, ol.

tinymceSettings позволяет изменять все настройки TinyMCE, но schema, valid_elements, extended_valid_elements, valid_children и custom_elements. Вы можете использовать SchemaBuilder для изменения этих свойств. В настоящее время открыт только метод withCustomElement, который позволяет добавлять пользовательский элемент вместе с разрешенными дочерними элементами и атрибутами.


/**
 * Registers a custom element along with allowed children.
 * The text node is added by default.
 *
 * @example
 * withCustomElement('x-task', ['p']);
 * withCustomElement('x-task', false);
 * withCustomElement('x-task');
 * withCustomElement('x-task', ['p'], ['content', 'done']);
 *
 * @param {string} name custom element name
 * @param {array.string|false=} children
 * when array provided: tag names of allowed children;
 * when false: no children allowed;
 * when undefined / unspecified: allow p and #comment nodes
 * @param {array.string=} attributes list of allowed attributes
 * @returns {SchemaBuilder}
 */
SchemaBuilder.prototype.withCustomElement = function (name, children, attributes);

Подсказка

Существуют некоторые проблемы при работе с текстовыми узлами непосредственно под пользовательским элементом. Для лучшего удобства пользователя содержимое вашего макроса должно быть завернуто в тег p.

Прикрепление макроса информации к панели инструментов

Нам нужно добавить кнопку на панель инструментов, а также выполнить правильное действие, когда пользователь нажимает на нее. Эта часть описана в учебнике - Написание плагина для редактора Rich Text в JIRA.

Ниже вы можете найти расширенный файл info-macro-init.js.

Листинг info-macro-init.js


require([
    "jquery",
    "jira/util/formatter",
    "jira/editor/registry",
    "jira/editor/customizer"
], function (
    $,
    formatter,
    editorRegistry,
    Customizer
) {
    var RefPlugin = window.RefPlugin;

    Customizer.customizeSettings(function (tinymceSettings, tinymce, SchemaBuilder) {
        SchemaBuilder.withCustomElement('info-macro', ['p', 'ul', 'ol']);
    });

    var INFO = formatter.I18n.getText('refplugin.toolbar.info');
    var INFO_PLACEHOLDER = formatter.I18n.getText('refplugin.macro.info.placeholder');
    var DROPDOWN_ITEM_HTML = '<li><a href="#" data-operation="info">' + INFO + '</a></li>';

    editorRegistry.on('register', function (entry) {
        var $otherDropdown = $(entry.toolbar).find('.wiki-edit-other-picker-trigger');

        $otherDropdown.one('click', function (dropdownClickEvent) {
            var dropdownContentId = dropdownClickEvent.currentTarget.getAttribute('aria-owns');
            var dropdownContent = document.getElementById(dropdownContentId);
            var speechItem = dropdownContent.querySelector('.wiki-edit-speech-item');

            var infoItem = $(DROPDOWN_ITEM_HTML).insertAfter(speechItem).on('click', function () {
                entry.applyIfTextMode(addWikiMarkup).applyIfVisualMode(addRenderedContent);
            });

            entry.onUnregister(function cleanup() {
                infoItem.remove();
            });
        });
    });

    function addWikiMarkup(entry) {
        var wikiEditor = $(entry.textArea).data('wikiEditor');
        var content = wikiEditor.manipulationEngine.getSelection().text || INFO_PLACEHOLDER;
        wikiEditor.manipulationEngine.replaceSelectionWith(RefPlugin.Macros.Info.wiki({content: content}));
    }

    function addRenderedContent(entry) {
        entry.rteInstance.then(function (rteInstance) {
            var tinyMCE = rteInstance.editor;
            if (tinyMCE && !tinyMCE.isHidden()) {
                var content = tinyMCE.selection.getContent() || INFO_PLACEHOLDER;
                tinyMCE.selection.setContent(RefPlugin.Macros.Info.html({ content: '<p>' + content + '</p>' }));
            }
        });
    };
});

Загрузка ресурсов в контекст редактора

Все ресурсы должны быть отражены в файле atlassian-plugin.xml. В этом разделе мы увидим пошаговое руководство по загрузке * info * макроресурсов. Порядок тэгов xml произволен.

Вы можете найти объяснение конкретных тегов xml, используемых в этом разделе, в Написание плагина для редактора Rich Text в JIRA.

Помещение * info * макроса на место

Листинг. Кусок atlassian-plugin.xml


<macro key='info' name='{info} formatting macro'
       class='com.atlassian.jira.plugin.editor.ref.InfoMacro'>
    <description>Allows you to insert a information banner.</description>
    <param name="convert-selector">info-macro</param>
    <param name="convert-function">RefPlugin.Macros.Info.convert</param>
</macro>

Добавление info-macro-init.js

Листинг. Кусок atlassian-plugin.xml


<macro key='info' name='{info} formatting macro'
       class='com.atlassian.jira.plugin.editor.ref.InfoMacro'>
    <description>Allows you to insert a information banner.</description>
    <param name="convert-selector">info-macro</param>
    <param name="convert-function">RefPlugin.Macros.Info.convert</param>
</macro>

Добавление информации о разметке HTML и Wiki info-macro.soy.js

Листинг. Кусок atlassian-plugin.xml


<web-resource key="handler" name="JIRA Editor Reference Plugin Context Init">
    <context>jira.rich.editor</context>

    <dependency>com.atlassian.jira.plugins.jira-editor-plugin:converter</dependency>

    <resource name="soy/info-macro.soy.js" type="download" location="soy/info-macro.soy" />

    <transformation extension="soy">
        <transformer key="soyTransformer"/>
    </transformation>
</web-resource>

Добавление модульных  тестов  info-macro-tests.js

Листинг. Кусок atlassian-plugin.xml


<resource type="qunit" name="js/info-macro-tests.js" location="/js/info-macro-tests.js" />

Добавление CSS info-macro.less

Листинг. Кусок atlassian-plugin.xml


<web-resource key="css" name="JIRA Editor Reference Plugin CSS Resources">
    <context>jira.view.issue</context>
    <context>gh-rapid</context>

    <context>jira.rich.editor.content</context>

    <transformation extension="less">
        <transformer key="lessTransformer"/>
    </transformation>

    <resource type="download" name="less/info-macro.css" location="less/info-macro.less"/>
</web-resource>

Добавление i18n

Листинг. Кусок atlassian-plugin.xml


<resource type="i18n" name="i18n" location="ref-i18n"/>

Ссылки

  • Исходный код плагина: bitbucket.org/atlassian/jira-editor-ref-plugin/overview
  • Документация TinyMCE: tinymce.com/docs/
  • Спецификация пользовательских элементов: www.w3.org/TR/custom-elements/

По материалам Atlassian JIRA  Server Developer Customizing Rich Text Editor in JIRA