IMBox に投稿種別を追加する方法(textile)

このCookBookでは、IMBoxに投稿種別を追加する方法について紹介しています。
以下のドキュメントを参照することで、IMBox に投稿種別を追加することができます。
IMBox プログラミングガイド - メッセージ種別追加プログラム

この CookBook では、投稿したメッセージを textile として解釈し HTML に変換し表示する投稿種別を追加します。
textile の解析には以下のライブラリを用います。
borgar/textile-js
textile の解析結果から script タグ等をサニタイズ際には以下のライブラリを用います。
cure53/DOMPurify

完成イメージ


1. 投稿種別に「メッセージ(textile)」を選択します。
2. メッセージを入力します。
3. 投稿します。

[iframe width="100%" height="400" src="https://dev-portal.intra-mart.jp/imart/imbox?imui-theme-builder-module=headwithcontainer"]

完成サンプル

以下の完成サンプルをダウンロードしてご活用ください。

e builder プロジェクト : im_cookbook_128115.zip
imm ファイル : im_cookbook_128115-1.0.0.imm

ローカル環境で表示させる場合は、以下のURLにアクセスしてください。
http://localhost:8080/imart/imbox
なおベースURLである以下の部分は、環境に合わせて適宜変更してください。
http://localhost:8080/imart

レシピ

  1. imbox-message-config を作成します。
  2. 投稿処理を作成します。
  3. タイムライン表示画面を作成します。
  4. textile-js を配置します。
  5. DOMPurify を配置します。

1. imbox-message-config を作成します。

src/main/conf/imbox-message-config/imbox-message-config_im_cookbook_128115.xml
<?xml version="1.0" encoding="UTF-8"?>
<imbox-message-config
    xmlns="http://www.intra-mart.jp/imbox/imbox-message-config"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.intra-mart.jp/imbox/imbox-message-config ../../schema/imbox-message-config.xsd">

  <public-message>
     <message-config
       display_flag="true"
       enabled_flag="true"
       sort_no="4"
       message_type_name="CAP.Z.IWP.COOKBOOK128115.MESSAGE.TYPE.NAME"
       post_type_path="im_cookbook_128115/message_type/views/type_textile"
       display_path="im_cookbook_128115/message_type/views/textile_timeline"
       message_type_cd="MESSAGE_TYPE_TEXTILE"/>
  </public-message>
</imbox-message-config>
行数 説明
12 メッセージ種別名称を設定します。
13 投稿欄表示用のファイルパスを設定します。
14 タイムライン表示用のファイルパスを設定します。

2. 投稿処理を作成します。

src/main/jssp/src/im_cookbook_128115/message_type/views/type_textile.js
var $imbox = {};

function init(request) {
    $imbox.identifier = Identifier.get();
}
src/main/jssp/src/im_cookbook_128115/message_type/views/type_textile.html
<style type="text/css">
#imui-container #imbox-message-type textarea.imbox-textarea{
   width: 98%;
   height: 45px;
   resize: none;
   overflow: hidden;
}
#imui-container #imbox-message-type div.imbox-button{
  padding-top: 10px;
  text-align: right;
}
</style>
<div>
  <form id="textile_form">
    <div class="mt-10">
      <imart type="imuiTextArea" id="textile-detail" class="imbox-textarea" name="textile-detail-name" placeholder="%CAP.Z.IWP.IMBOX.UI.COMMON.HELP.SEND.MESSAGE"></imart>
    </div>
  </form>
</div>
<form id="imbox_timeline_send_form" action="ui/filer/upload" method="POST" enctype="multipart/form-data">
  <imart type="include" page="imbox/views/timeline/item/add_options" />
</form>
<div id="textile_button" class="imbox-button mt-20 align-R">
  <button type="button" id="textile_send_button" class="imui-medium-button"><imart type="message" id="CAP.Z.IWP.IMBOX.CONTRIB.POLL.POST" /></button>
</div>
<script type="text/javascript">
(function($) {
  $('#textile_send_button').click(function() {
    var button = $(this);
    var attributes = {
      identifier: '<imart type="string" value=$imbox.identifier escapeJs="true" />',
      message: $('#textile-detail').val()
    };
    var messageTypeCd = $('.imbox-timeline-sendtype-list').children(':selected').val();
    var boxCd = ($imbox.timeline.clientType === $imbox.constants.CLIENT_TYPE_LIST['COMPONENTS']) ? $imbox.timeline.boxCd: $('.imbox-timeline-grouptype-list').children(':selected').val();
    var url = 'imbox/send/' + encodeURIComponent(boxCd);
    var attachFlag = '0';
    var files = [];
    var tagNames = [];
    $('.imbox-timeline-send-tag-list-hidden').each(function() {
      tagNames.push($(this).val());
    });

    if (tagNames.length){
      attachFlag = '1';
    }

    for (var i = 0, length = $imbox.fileName.length; i < length; i++) {
      if ($imbox.fileName[i].key === 'imbox_timeline_send_form') {
        files.push($imbox.fileName[i].name);
      }
    }

    var data = {
      'send_message': 'DONT_USE',
      'messageTypeCd': messageTypeCd,
      'displayId': $imbox.timeline.displayId,
      'timelineType': $imbox.timeline.timelineType,
      'clientType': $imbox.timeline.clientType,
      'attributes': ImJson.toJSONString(attributes, false),
      'attachName[]': files,
      'attachPath': $('#imbox_timeline_send_attach_file_name').data('store_to'),
      'attachFlag': attachFlag,
      'tag_name': tagNames
    };

    if (!imuiValidate('#imbox_timeline_send_form', imboxTimelineSendRules, imboxTimelineSendMessage, '')) {
      return false;
    }
    var success = $imbox.ajax.send('POST', url, data, $imbox.timeline.sendCallback, button);

    if (success) {
      $('#textile-place').val('');
      imuiResetForm('#imbox_timeline_send_form');
    }
  });
}(jQuery));
</script>
行数 説明
32 メッセージを取得します。
55 メッセージをアトリビュートで受け取るため本属性は使用しません。そのためダミーのデータを設定します。
60 アトリビュートを JSON に変換します。

3. タイムライン表示画面を作成します。

src/im_cookbook_128115/message_type/views/textile_timeline.js
var $imbox = {};

/**
 * textile用タイムライン画面初期処理
 * @param  {Object} request リクエストパラメータ
 */
function init(request) {
  let message = request.message;
  let attributes = request.message.attributes;

  $imbox.messageText = attributes.message.toSource().replace(/<\/script(\s*)>/gim, '<\\/script$1>'); // </script> を無効化
  $imbox.identifier = attributes.identifier;
  $imbox.message = message;
}
行数 説明
11 送信されたメッセージを取得します。
src/im_cookbook_128115/message_type/views/textile_timeline.html
<div class="imbox-timeline-thread-post-right-body">
  <p>
    <a href="imbox/usermessage/<imart type="string" value=$imbox.message.encodePostUserCd escapeXml="true" escapeJs="false" />" class="imbox-timeline-thread-post-user-name">
      <imart type="string" value=$imbox.message.postUserName escapeXml="true" escapeJs="false" />
    </a>
    <span class="imbox-timeline-thread-from-to-icon imbox-icon-common-16-arrow"></span>
    <imart type="string" value=$imbox.message.postTypeInfo.postToName escapeXml="true" escapeJs="false" />
  </p>
  <div class="imbox-timeline-thread-post-right-body">
    <div class="imbox-timeline-thread-message">
      <div class="imbox-timeline-thread-message-area">
        <div class="imbox-timeline-thread-message-text">
          <div class="cf">
            <imart type="tag" tagname="div" class="float-L mt-10" id=$imbox.identifier />
            <script type="text/javascript">
              (function ($) {
                $.getScript('csjs/libs/textile-js/lib/textile.min.js').done(function(script, textStatus) {
                  $.getScript('csjs/libs/DOMPurify/dist/purify.min.js').done(function(script, textStatus) {
                    var text = <imart type="string" value=$imbox.messageText escapeXml="false" escapeJs="false" />;
                    var html = textile.parse(text)
                                      .replace(/<h1>(.+?)<\/h1>/gim, '<div class="imui-title-small-window"><h1>$1</h1></div>') // <h1>
                                      .replace(/<h2>(.+?)<\/h2>/gim, '<div class="imui-chapter-title"><h2>$1</h2></div>') // <h2>
                                      .replace(/<h3>(.+?)<\/h3>/gim, '<div class="imui-section-title"><h3>$1</h3></div>') // <h3>
                                      .replace(/<h4>(.+?)<\/h4>/gim, '<div class="imui-subsection-title"><h4>$1</h4></div>') // <h4>
                                      .replace(/<h5>(.+?)<\/h5>/gim, '<div class="imui-paragraph-title"><h5>$1</h5></div>') // <h5>
                                      .replace(/<h6>(.+?)<\/h6>/gim, '<div class="imui-subparagraph-title"><h6>$1</h6></div>') // <h6>
                                      .replace(/<table>/gim, '<table class="imui-table"><tbody>') // <table>
                                      .replace(/<\/table>/gim, '</tbody></table>') // </table>
                                      .replace(/<ul>/gim, '<ul class="imui-list">'); // <ul>
                    var sanitized = DOMPurify.sanitize(html);

                    $('#<imart type="string" value=$imbox.identifier escapeXml="false" escapeJs="true" />').empty().append(sanitized);
                  });
                });
              }(jQuery));
            </script>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
行数 説明
19 メッセージを取得します。
20~29 メッセージをパースし html に変換します。その際、いくつかのクラスにスタイルを追加するために文字列の置換を行います。
30 変換後の html をサニタイズします。
32 変換後の html を表示します。

4. textile-js を配置します。

以下から textile-js をダウンロードし、textile-js-master/lib/textile.min.js を Web サーバの %CONTEXT_PATH%/csjs/libs/textile-js/lib/textile.min.js に配置します。
borgar/textile-js

5. DOMPurify を配置します。

以下から DOMPurify をダウンロードし、DOMPurify-master/dist/purify.min.js を Web サーバの %CONTEXT_PATH%/csjs/libs/DOMPurify/dist/purify.min.js に配置します。
cure53/DOMPurify

まとめ

この CookBook では、textile を入力できるメッセージ種別を追加する方法を紹介しました。

使用するライブラリを変更するだけで Markdown に対応することも可能です。
お客様の用途にあわせて是非ご活用ください。