В Atlassian Confluence есть замечательный функционал сравнения версий страницы. Им удобно пользоваться, но выйти за пределы истории изменений не удастся. Как же быть? Поиск готового плагина в Marketplace результатов не дал и было принято решение написать свой.

Инструменты


Нам понадобятся:

  • Oracle JDK или AdoptOpenJDK 1.8, работа с OpenJDK официально не поддерживается;
  • Atlassian SDK — можно скачать с официального сайта для Windows и Linux/Mac без СМС и регистрации;
  • Apache Maven;
  • Любимая среда разработки, у меня IntelliJ IDEA.

Создадим проект


Установим SDK и выполним скрипт atlas-create-confluence-plugin.

Вводим:

  • group-id: com.kshch.confluence.plugins
  • artifact-id: diff-page
  • version: 1.0.0-SNAPSHOT
  • package: com.kshch.confluence.plugins.diff.page

Для сборки можно воспользоваться maven-ом и зависимостями из SDK, но проще подключить дополнительные репозитории в своем settings.xml.

<repository>
    <id>atlassian-public</id>
    <url>https://packages.atlassian.com/maven/repository/public</url>
    <snapshots>
        <enabled>true</enabled>
        <updatePolicy>never</updatePolicy>
        <checksumPolicy>warn</checksumPolicy>
    </snapshots>
    <releases>
        <enabled>true</enabled>
        <checksumPolicy>warn</checksumPolicy>
    </releases>
</repository>

<pluginRepository>
    <id>atlassian-public</id>
    <url>https://maven.atlassian.com/repository/public</url>
    <releases>
        <enabled>true</enabled>
        <checksumPolicy>warn</checksumPolicy>
    </releases>
    <snapshots>
        <updatePolicy>never</updatePolicy>
        <checksumPolicy>warn</checksumPolicy>
    </snapshots>
</pluginRepository>

Откроем получившийся проект в IDE, запустим Confluence в режиме отладки confluence:debug



и проверим работу локальной установки по адресу http://localhost:1990/confluence/. Логин admin, пароль admin. В базе уже создано демонстрационное пространство и несколько статей.

Немного функционала


Начнем с меню. Добавим в дескриптор плагина atlassian-plugin.xml модуль web-item.

<web-item key="diff-page-menu" name="Diff Page" section="system.content.action/secondary" weight="300">
    <description>Add diff item to drop-down menu</description>
    <label key="diff.page.menu.name"/>
    <link linkId="diff-page">/plugins/diffPage/diffPagePopup.action?spaceKey=${space.key}&sourcePageId=${page.id}</link>
    <condition class="com.atlassian.confluence.plugin.descriptor.web.conditions.HasPageCondition"/>
</web-item>

Где:

  • атрибут section — определяет место появления пункта меню;
  • атрибут weight — отвечает за порядок сортировки;
  • атрибут key тега label — ключ, по которому в файлах ресурсов находится название отображаемого пункта меню;
  • тег link — содержит ссылку, а атрибут linkId ее id;
  • condition — определяет условие появления, в нашем случае только на страницах.

Добавляем в diff-page.properties строку:
diff.page.menu.name=Diff page

Если нужна поддержка переводов — создадим дополнительные property, например, для русской локализации файл будет называться diff-page_ru_RU.properties.

Выполним maven package. Плагин будет автоматически перезагружен и появится наш пункт меню.



Модальное окно


Для выбора страницы удобно использовать модальное окно. Продолжим править atlassian-plugin.xml

Добавим немного ресурсов:

<web-resource key="diff-page-popup-resources" name="diff-page Popup Web Resources">
    <dependency>com.atlassian.auiplugin:ajs</dependency>
    <dependency>com.atlassian.auiplugin:dialog2</dependency>
    <resource type="download" name="diff-page-popup.js" location="/js/diff-page-popup.js"/>
    <context>page</context>
</web-resource>

Где:

  • тег dependency — зависимость;
  • в атрибутах resource указаны тип ресурса, имя и расположение на файловой системе;
  • тег context — определяет видимость в зависимости от контекста.

Определим обработчики наших url-ов:

<xwork name="Diff Page Action" key="diff-page-action">
    <description>Diff page action</description>
    <package name="diff-page-package" extends="default" namespace="/plugins/diffPage">
        <default-interceptor-ref name="defaultStack"/>
        <action name="diffPage"
                class="com.kshch.confluence.plugins.diff.page.action.DiffPageAction">
            <result name="success" type="velocity">/templates/diff-page.vm</result>
        </action>
        <action name="diffPagePopup"
                class="com.kshch.confluence.plugins.diff.page.action.DiffPagePopupAction">
            <result name="success" type="velocity">/templates/diff-page-popup.vm</result>
        </action>
    </package>
</xwork>

Где:

  • атрибут name тега action — определяет название ссылки, а class — java class для ее обработки;
  • тег result — содержит ссылку на шаблон, в нашем случае velocity.

Создадим новый пакет com.kshch.confluence.plugins.diff.page.action и добавим в него класс DiffPagePopupAction, расширяющий ConfluenceActionSupport с 2-мя полями.

private String spaceKey;
private Long sourcePageId;

Сгенерируем к ним геттеры и сеттеры. В поле spaceKey будет содержаться ключ текущего пространства, а в sourcePageId — id текущей страницы.

Займемся фронтом. Для создания всплывающего окна воспользуемся элементом dialog2 из фреймворка AUI.

Добавим в диалог форму, кнопку submit и input-ы с css классами autocomplete-space и autocomplete-page, что позволит без дополнительных усилий организовать выбор пространства и страницы. Сохраним полученный результат в /templates/diff-page-popup.vm.

<section role="dialog" id="diff-page-popup" class="aui-layer aui-dialog2 aui-dialog2-medium" aria-hidden="true"
         data-aui-remove-on-hide="true">
    <form action="$action.getBootstrapManager().getWebAppContextPath()/plugins/diffPage/diffPage.action" method="get" class="aui">
...
<input type="hidden" name="sourcePageId" value="$action.getSourcePageId()">
<input class="text autocomplete-space" type="text" id="diff-page-space"
                       name="spaceKey" data-max="10" data-none-message="$action.getText("diff.page.popup.no.result")"
                       placeholder="$action.getText("diff.page.popup.select.space")" value="$action.getSpaceKey()" data-template="{key}">
<input type="text" class="text autocomplete-page" name="destinationPageName" data-max="10"
                       placeholder="$action.getText("diff.page.popup.select.page")" data-none-message="$action.getText("diff.page.popup.no.result")">
<button id="dialog-diff-button" class="aui-button aui-button-primary">$action.getText("diff.page.popup.diff")</button>
...
    </form>
</section>

Для отображения окна создадим в ресурсах /js/diff-page-popup.js со следующим содержимым:

(function ($) {
    $(function () {
        AJS.$('#diff-page').unbind('click');
        AJS.$('#diff-page').bind("click", function (e) {
            e.preventDefault();
            var link = AJS.$(this);
            AJS.$.get(link.attr('href'), function (response) {
                AJS.$('.aui-page-panel').after(response);
                AJS.dialog2("#diff-page-popup").show();
                Confluence.Binder.autocompletePage(AJS.$("#diff-page-popup-binder"));
            });
            return false;
        });
    });
})(AJS.$);

Его задача переопределить стандартное событие click и показать нам всплывающее окно, а не осуществить переход по ссылке.

Выполним maven package и попробуем выбрать страницу и пространство. Подсказчик активируется после набора 2-х символов.



Основной функционал


Приступим к реализации основного функционала. Добавим в пакет com.kshch.confluence.plugins.diff.page.action еще один класс DiffPageAction, расширяющий ConfluenceActionSupport и реализующий интерфейс PageAware со следующими полями.

private Long sourcePageId;
private Long destinationPageId;
private String spaceKey;
private String destinationPageName;
private String sourcePageTitle;
private String destinationPageTitle;
private String diff;
private Page sourcePage;
private Differ differ;
private final PageManager pageManager;

Где:

  • sourcePageId — id исходной страницы;
  • destinationPageId — id сравниваемой страницы;
  • spaceKey — ключ пространства;
  • destinationPageName — имя сравниваемой страницы;
  • sourcePageTitle — заголовок исходной страницы;
  • destinationPageTitle — заголовок сравниваемой страницы;
  • diff — результат сравнения.

Сгенерируем геттеры и сеттеры, реализуем все методы, кроме getPage, как предлагает IDE по умолчанию. В полях sourcePageId, spaceKey и destinationPageName будут содержаться данные, которые пришли из формы внутри dialog2.

@Override
public AbstractPage getPage() {
    return this.sourcePage;
}

Переопределим метод execute своим. Именно в нем и заключена основная логика работы плагина.

@Override
public String execute() throws Exception {
    if (this.sourcePageId != null && this.spaceKey != null && this.destinationPageName != null) {
        this.sourcePage = this.pageManager.getPage(this.sourcePageId);
        Page destinationPage = this.pageManager.getPage(this.spaceKey, this.destinationPageName);
        if (this.sourcePage != null && destinationPage != null) {
            this.destinationPageId = destinationPage.getId();
            this.sourcePageTitle = this.sourcePage.getTitle();
            this.destinationPageTitle = destinationPage.getTitle();
            this.diff = this.differ.diff(this.sourcePage, destinationPage);
         }
    }
    return super.execute();
}

Остается реализовать frontend сравнения 2-х страниц. Он гораздо проще всплывающего окна.

Создадим в ресурсах /templates/diff-page.vm. В нем, кроме верстки, реализуем простейшую проверку на ошибки.

#if ($action.getDiff())
    #diffBody()
#else
    <div class="aui-message aui-message-error">
        <p class="title">
            <strong>$action.getText("diff.page.error")</strong>
        </p>
        <p>$action.getText("diff.page.error.message")</p>
    </div>
#end

Написание плагина закончено. Еще раз выполним maven package, выберем страницу и насладимся полученным результатом.



Полный код проекта доступен на GitHub.

Комментарии (1)


  1. gonchik
    17.12.2018 20:19

    Спасибо за статью! Будут ли ещё продолжения?