| 
 Уровень опыта  | 
 продвинутый  | 
| 
 Временная оценка  | 
 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