Pleasanterの説明項目の文字に色をつけたい

# はじめに
プリザンターは一覧表示で情報を管理する用途では、思い立ってからアプリが動き出すまでが圧倒的短時間で作れるノーコードツールです。多くのツールは白紙のキャンバスを前に途方に暮れるのですが、プリザンターはこの段階が無いのが大きな利点でお気に入りです。
……お気に入りなのですが、ものすごく不満と言いますか、ダメだろコレと思う部分があるんです。それは、文章の文字に「色」を付けることが出来ないところです。
リッチテキストの編集機能とまでは言いませんが、やはり説明文書の注意事項は赤文字にしたいじゃないですか！

# 方針
本体のソースコードをいじる根性は無いため、ブラウザに表示した後で文字に色を付けたいと思います。プリザンターの独自JavaScriptコードをロードし放題の最高拡張機能を使って、色を付けたい文字を`<span>`や`<font>`タグで囲えば行けそうです。(ブラウザのDevToosを使って書き換えたら色がつきました)

とは言っても色指定の修飾タグを独自に考えるのは面倒なのでMarkdownの場合のみ変更したいと思います。プリザンターが`<span>`を`&lt;span&gt;`とエスケープしてくれている部分を元に戻すわけですね。

# 検証環境
Pleasanter:1.3.20.0
web browser:chrome

# ソースコード
<details><summary>サンプルコード</summary>

```javascript
//////////////////////////////////////////////////
// MarkDownモードの時に<sapn><font>タグで文字色を指定可能とする
// 実現方法は編集画面のHTMLを書き換えているだけ。根本解決では無い
//////////////////////////////////////////////////

//////////////////////////////////////////////////
// 監視に使うSelectorの作成
// columnName:英語の項目名(Body,DescriptionA〜Z)
//////////////////////////////////////////////////
function makeSelector(columnName){
  let selectorSet = {};   // 返却用データの入れ物
  let selector_target     // 変更検出用セレクタ（MutationObserverの監視node）

  // 各テーブルタイプの判定
  switch ($p.tableName()) {
    case 'Issues':
        // 期限付きテーブル
        selector_target = '#Issues_' + columnName + '\\.viewer';
        break;
    case 'Results':
        // 記録テーブル
        selector_target = '#Results_' + columnName + '\\.viewer';
        break;
    case 'Sites':
    case 'Wikis':
    default:
      return selectorSet;
  }

  // MarkDown領域の検出用セレクタ作成
  let selector_md = selector_target + '>div.md';
  let selector_html_span_Tag = '>p:contains("<span")';  // 文字色対応用("<span style="にした方が安全だと思う)
  let selector_html_font_Tag = '>p:contains("<font")';  // 文字色対応用("<font style="にした方が安全だと思う)
  let selector_html_Code_Tag = '>p>code';               // [`]対応用(インラインコード：応用例)
  let selector_html_Code2_Tag = '>pre>code';            // [```]対応用(コードブロック：応用例)
  let selector_html_Tag = selector_md + selector_html_span_Tag;
  selector_html_Tag += ','+selector_md + selector_html_font_Tag;
  selector_html_Tag += ','+selector_md + selector_html_Code_Tag;
  selector_html_Tag += ','+selector_md + selector_html_Code2_Tag;

  selectorSet['columnName'] = columnName;
  selectorSet['selector_target'] = selector_target;
  selectorSet['selector_md'] = selector_md;
  selectorSet['selector_html_Tag'] = selector_html_Tag;

  return(selectorSet);
}

//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元 <span>
//////////////////////////////////////////////////
function restoreHtmlTag_span(contentsText){
  // 書式が固定できるなら検索ルールは厳しめにした方が安全
  return contentsText.replace(/&lt;span([0-9a-zA-Z=\-:\.,; "'=\t]*?)&gt;(.*?)&lt;\/span&gt;/g, '<span$1>$2</span>');
}

//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元 <font>
//////////////////////////////////////////////////
function restoreHtmlTag_font(contentsText){
  // 書式が固定できるなら検索ルールは厳しめにした方が安全
  return  contentsText.replace(/&lt;font([0-9a-zA-Z=\-:\.,; "'=\t]*?)&gt;(.*?)&lt;\/font&gt;/g, '<font$1>$2</font>');
}

//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元　<code>
//////////////////////////////////////////////////
function restoreHtmlTag_code(contentsText){
  // 書式が固定できるなら検索ルールは厳しめにした方が安全
  // [`]インラインコードと[```]コードブロックの対応用<code>タグの中
  return contentsText.replace(/&amp;lt;([0-9a-zA-Z=\-:\.,; "'=\/\t]*?)&amp;gt;/g, '&lt;$1&gt;');
}

//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元　<br>
//////////////////////////////////////////////////
function restoreHtmlTag_br(contentsText){
  // 書式が固定できるなら検索ルールは厳しめにした方が安全だと思う。
//  return contentsText.replace(/(?<!<code>)&lt;br&gt;(?!<code>)/g, '<br>');
  return contentsText.replace(/(?<!<code>)&lt;br&gt;(?!<code>)/g, '<br>');
}

//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元
//////////////////////////////////////////////////
function restoreHtmlTag(contentsText){
  return restoreHtmlTag_code(restoreHtmlTag_font(restoreHtmlTag_span(contentsText)));
}

//////////////////////////////////////////////////
// コメント欄のMarkdown変更
//////////////////////////////////////////////////
function commentRestoreHtmlTag() {
  let selector_comment_Tag = '#CommentList p.body.markup.applied>div.md';     // MarkDown領域検出用セレクタ作成
  let commentList = document.querySelectorAll(selector_comment_Tag);
  commentList.forEach((commentListElm,index_commentList) => {
    $(commentListElm).html(restoreHtmlTag(restoreHtmlTag_br($(commentListElm).html())));
  });
};

//////////////////////////////////////////////////
// 内容項目と説明項目のMarkdown変更
//////////////////////////////////////////////////
function descriptionRestoreHtmlTag(selectorSet) {
  let divMds = document.querySelectorAll(selectorSet.selector_md);
  divMds.forEach((divMd,index_Md) => {
    $(divMd).html(restoreHtmlTag_br($(divMd).html()));  // div.mdノード全体で<br>改行対応
    let markdowntNodes = $(selectorSet.selector_html_Tag);
    markdowntNodes.each((index,markdownElm) => {
      $(markdownElm).html(restoreHtmlTag($(markdownElm).html()));
    });
  });
}

Markdown_ThroughHtmlTag:{
  let selectorSets = {};    // 監視対象のselector情報

  //////////////////////////////////////////////////
  // 内容項目と説明項目のDOM変更を監視してDOMの上書きを行う
  //////////////////////////////////////////////////
  function setDescriptionRestoreHtmlTag(){
    let hashIdArray = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];

     // 説明項目と内容項目のセレクタを作成
    let columnName = 'Body';
    let selectorSet = makeSelector(columnName);
    if(Object.keys(selectorSet).length != 0){
      selectorSets[columnName] = selectorSet;
    }
    hashIdArray.forEach((hashId) => {
      columnName = 'Description'+ hashId;
      selectorSet = makeSelector(columnName);
      if(Object.keys(selectorSet).length != 0){
        selectorSets[columnName] = selectorSet;
      }
    });

    // 内容項目と説明項目の変更を監視開始
    Object.keys(selectorSets).forEach(key => {
      selectorSets[key]['observer'] = setMutationObserver(selectorSets[key]);
    });

    //////////////////////////////////////////////////
    // DOM変更を監視の登録
    function setMutationObserver(selectorSet){
      // nodeの存在確認
      let node = document.querySelectorAll(selectorSet.selector_target);
      if(node.length == 0){
        return;
      }
      let observeNode = node[0]; // 監視対象のNodeをセット

      // オブザーバの設定値
      const observeConfig = {
        // childList、attributes、characterDataいずれかにtrue必須
        childList: true,       // 対象ノードの子ノード（テキストノードも含む）に対する追加・削除を監視する場合はtrue。
        attributes: true,      // 対象ノードの属性に対する変更を監視する場合はtrue。
        characterData: true,   // 対象ノードのデータに対する変更を監視する場合はtrue。
        subtree: true          // 対象ノードの子孫ノードまで監視する場合はtrue。
      }

      // オブザーバインスタンスを作成
      const observer = new MutationObserver((mutations) => {
        // 更新検出時のコールバック関数
        console.log( "内容項目と説明項目 変更検出個数"+ mutations.length);
        observer.disconnect();                          // 監視の停止
        descriptionRestoreHtmlTag(selectorSet);         // 内容項目・説明項目のMarkdownの書換処理
        observer.observe(observeNode, observeConfig);   // 監視の再開
      });
      // オブザーバの開始
      observer.observe(observeNode,observeConfig);

      return(observer);
    }
  }

  //////////////////////////////////////////////////
  // コメント欄のDOM変更を監視してDOMの上書きを行う
  //////////////////////////////////////////////////
  function setCommentRestoreHtmlTag(){
    // nodeの存在確認
    let node;
    node = document.querySelectorAll('#CommentList');
    if(node.length == 0){
      return;
    }
    let observeNode = node[0]; // 監視対象のNodeをセット

    // オブザーバの設定値
    const observeConfig = {
      // childList、attributes、characterDataいずれかにtrue必須
      childList: true,       // 対象ノードの子ノード（テキストノードも含む）に対する追加・削除を監視する場合はtrue。
      attributes: true,      // 対象ノードの属性に対する変更を監視する場合はtrue。
      characterData: true,   // 対象ノードのデータに対する変更を監視する場合はtrue。
      subtree: true          // 対象ノードの子孫ノードまで監視する場合はtrue。
    }

    // オブザーバインスタンスを作成
    const observer = new MutationObserver((mutations) => {
      // 更新検出時のコールバック関数
      console.log( "コメント欄 変更検出個数"+ mutations.length);
      observer.disconnect();                          // 監視の停止
      commentRestoreHtmlTag();                         // コメント欄のMarkdownの書換処理
      observer.observe(observeNode, observeConfig);   // 監視の再開
    });
    // オブザーバの開始
    observer.observe(observeNode,observeConfig);

    return(observer);
  }

  // 「更新」ボタンを押した場合の処理
  $p.events.after_set_Update = function () {
    // この時点でPleasanterがDOMの動的更新が完成している。
    commentRestoreHtmlTag();                        // コメント欄のMarkdownを更新
    Object.keys(selectorSets).forEach(key => {
      descriptionRestoreHtmlTag(selectorSets[key])  // 説明項目と内容項目のMarkdownを更新
    });

    // 「更新」ボタンでDOMが再構築されため再度監視設定
    setDescriptionRestoreHtmlTag();
  }

  // ブラウザで編集画面を読み込んだ時の処理(開いた時と再読み込みした時)
  document.addEventListener( "DOMContentLoaded", () => {
    // DOM構築完了後に実行
    // この時点でPleasanterのDOMの動的更新は完成していない。
    setCommentRestoreHtmlTag();       // コメント欄のMarkdownを監視設定
    setDescriptionRestoreHtmlTag();   // 説明項目と内容項目のMarkdownを監視設定
  });
}
```

</details>

実行方法
1. 「サンプルコード」を開いてコードを表示する。
1. ソースコードをコピーする。
1. プリザンターの「テーブルの管理」を開く
1. 「スクリプト」タブで新規作成
1. ソースコードをペーストする。
1. 出力先に「編集」を選択する。

# 解説

スクリプトを作るには、変換する`<span>`タグの場所がわからないと話になりませんからDevToolを使って探します。
DevToolでDOMの構造を見ると、内容項目（Body）のMarkdownの場合は`#Issues_Body.viewer`の下に`div.md`ができて、子エレメントの`<p>`タグの中に文字列が入ってるようです。
これで検索する場所がわかりました。

はじめに必要な部品を作っていきます。
検索用のセレクタは`Body`と`DescriptionA〜Z`分を作る必要があるためセレクタを作成する関数をつくります。
```javascript
//////////////////////////////////////////////////
// 監視に使うSelectorの作成
// columnName:英語の項目名(Body,DescriptionA〜Z)
//////////////////////////////////////////////////
function makeSelector(columnName){
  let selectorSet = {};   // 返却用データの入れ物
  let selector_target     // 変更検出用セレクタ（MutationObserverの監視node）

  // 各テーブルタイプの判定
  switch ($p.tableName()) {
    case 'Issues':
        // 期限付きテーブル
        selector_target = '#Issues_' + columnName + '\\.viewer';
        break;
    case 'Results':
        // 記録テーブル
        selector_target = '#Results_' + columnName + '\\.viewer';
        break;
    case 'Sites':
    case 'Wikis':
    default:
      return selectorSet;
  }

  // MarkDown領域の検出用セレクタ作成
  let selector_md = selector_target + '>div.md';
  let selector_html_span_Tag = '>p:contains("<span")';  // 文字色対応用("<span style="にした方が安全だと思う)
  let selector_html_font_Tag = '>p:contains("<font")';  // 文字色対応用("<font style="にした方が安全だと思う)
  let selector_html_Code_Tag = '>p>code';               // [`]対応用(インラインコード：応用例)
  let selector_html_Code2_Tag = '>pre>code';            // [```]対応用(コードブロック：応用例)
  let selector_html_Tag = selector_md + selector_html_span_Tag;
  selector_html_Tag += ','+selector_md + selector_html_font_Tag;
  selector_html_Tag += ','+selector_md + selector_html_Code_Tag;
  selector_html_Tag += ','+selector_md + selector_html_Code2_Tag;

  selectorSet['columnName'] = columnName;
  selectorSet['selector_target'] = selector_target;
  selectorSet['selector_md'] = selector_md;
  selectorSet['selector_html_Tag'] = selector_html_Tag;

  return(selectorSet);
}
```
次に文字列を変換する関数を作ります。
プリザンターは`<span style="color:red">`タグは`&lt;span style="color:red"&gt;`に変換しますから、この逆の処理をすればブラウザが認識するHtmlタグの完成です。
しかし、単純に置き換えたらコードブロックやインラインコードの`&lt;span style="color:red"&gt;`もHtmlタグに戻るため除外する対応が必要です。と思いましたが、プリザンターはコードブロックの中の`<span style="color:red">`は`&amp;lt;span style="color:red"&amp;gt;`に置き換えていました。別の文字列のため検索に引っかかりません。やったね！
（でも、このままではコードブロックの見た目が悪いので対応しますよ。）

以下の正規表現は「良く解らないけど動作しているからヨシ！」なので適時修正してください。
(正規表現は魔法の呪文として記憶してるので…)

```javascript
//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元 <span>
//////////////////////////////////////////////////
function restoreHtmlTag_span(contentsText){
  // 書式が固定できるなら検索ルールは厳しめにした方が安全
  return contentsText.replace(/&lt;span([0-9a-zA-Z=\-:\.,; "'=\t]*?)&gt;(.*?)&lt;\/span&gt;/g, '<span$1>$2</span>');
}

//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元 <font>
//////////////////////////////////////////////////
function restoreHtmlTag_font(contentsText){
  // 書式が固定できるなら検索ルールは厳しめにした方が安全
  return  contentsText.replace(/&lt;font([0-9a-zA-Z=\-:\.,; "'=\t]*?)&gt;(.*?)&lt;\/font&gt;/g, '<font$1>$2</font>');
}

//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元　<code>
//////////////////////////////////////////////////
function restoreHtmlTag_code(contentsText){
  // 書式が固定できるなら検索ルールは厳しめにした方が安全
  // [`]インラインコードと[```]コードブロックの対応用<code>タグの中
  return contentsText.replace(/&amp;lt;([0-9a-zA-Z=\-:\.,; "'=\/\t]*?)&amp;gt;/g, '&lt;$1&gt;');
}

//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元　<br>
//////////////////////////////////////////////////
function restoreHtmlTag_br(contentsText){
  // 書式が固定できるなら検索ルールは厳しめにした方が安全だと思う。
  return contentsText.replace(/(?<!<code>)&lt;br&gt;(?!<code>)/g, '<br>');
}
```
:::note info
Safariは後読み正規表現には対応してませんから、`contentsText.replace(/(?<!<code>)&lt;br&gt;(?!<code>)/g, '<br>');`で例外がでます。以下のような感じで逃げるなどしてください。(正規表現は良く解らないから、正規表現から逃げてますね)
```javascript
let text = contentsText.replace(/<code>&lt;br&gt;<code>/g, '<code>&amp;lt;br&amp;gt;<code>');
text = text.replace(/&lt;br&gt;/g, '<br>');
text = text.replace(/<code>&amp;lt;br&amp;gt;<code>/g, '<code>&lt;br&gt;<code>');
```
:::

実行順番に依存関係がありそうで、複数箇所で使う変換をまとめて関数にします。
```javascript
//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元
//////////////////////////////////////////////////
function restoreHtmlTag(contentsText){
  return restoreHtmlTag_code(restoreHtmlTag_font(restoreHtmlTag_span(contentsText)));
}
```
DOMからセレクタでノードを取得して文字列を置換する関数です。ループを回しているだけですね。コメント欄用もつくりました。
JQueryを使ったり使わなかったりしている部分は、`selector_html_Tag`の中にJQueryでしか使えないセレクタが入っているからです。
```javascript
//////////////////////////////////////////////////
// コメント欄のMarkdown変更
//////////////////////////////////////////////////
function commentRestoreHtmlTag() {
  let selector_comment_Tag = '#CommentList p.body.markup.applied>div.md';     // MarkDown領域検出用セレクタ作成
  let commentList = document.querySelectorAll(selector_comment_Tag);
  commentList.forEach((commentListElm,index_commentList) => {
    $(commentListElm).html(restoreHtmlTag(restoreHtmlTag_br($(commentListElm).html())));
  });
};

//////////////////////////////////////////////////
// 内容項目と説明項目のMarkdown変更
//////////////////////////////////////////////////
function descriptionRestoreHtmlTag(selectorSet) {
  let divMds = document.querySelectorAll(selectorSet.selector_md);
  divMds.forEach((divMd,index_Md) => {
    $(divMd).html(restoreHtmlTag_br($(divMd).html()));  // div.mdノード全体で<br>改行対応
    let markdowntNodes = $(selectorSet.selector_html_Tag);
    markdowntNodes.each((index,markdownElm) => {
      $(markdownElm).html(restoreHtmlTag($(markdownElm).html()));
    });
  });
}
```
ここからが処理フローになります。フロー部は`Markdown_ThroughHtmlTag:{`とラベル付きブロックで全体を囲っていますが、グローバルスコープの`let selectorSets`を消せなかったため、気持ち悪いので`{}`で囲っています。(囲わなくても良いです。)

初めにBodyとDescriptionA〜Zのセレクタを作ります。
そして、内容項目と説明項目の変更の監視を開始します。
当初は`$p.events.on_editor_load`のイベントで変換処理をすれば良いと考えていましたが、よくよく考えたら編集画面ロード後に内容項目や説明項目は内容を変更しますよね。
毎回、「変更」->「更新」->「リロード」と言う仕様は無いですから`MutationObserver`でDOMの変更を監視しています。
```javascript
Markdown_ThroughHtmlTag:{
  let selectorSets = {};    // 監視対象のselector情報

  //////////////////////////////////////////////////
  // 内容項目と説明項目のDOM変更を監視してDOMの上書きを行う
  //////////////////////////////////////////////////
  function setDescriptionRestoreHtmlTag(){
    let hashIdArray = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];

     // 説明項目と内容項目のセレクタを作成
    let columnName = 'Body';
    let selectorSet = makeSelector(columnName);
    if(Object.keys(selectorSet).length != 0){
      selectorSets[columnName] = selectorSet;
    }
    hashIdArray.forEach((hashId) => {
      columnName = 'Description'+ hashId;
      selectorSet = makeSelector(columnName);
      if(Object.keys(selectorSet).length != 0){
        selectorSets[columnName] = selectorSet;
      }
    });

    // 内容項目と説明項目の変更を監視開始
    Object.keys(selectorSets).forEach(key => {
      selectorSets[key]['observer'] = setMutationObserver(selectorSets[key]);
    });
```

`MutationObserver`の処理は教科書通りです。
(ほんと`MutationObserver`は便利ですよね。)

```javascript
    //////////////////////////////////////////////////
    // DOM変更を監視の登録
    function setMutationObserver(selectorSet){
      // nodeの存在確認
      let node = document.querySelectorAll(selectorSet.selector_target);
      if(node.length == 0){
        return;
      }
      let observeNode = node[0]; // 監視対象のNodeをセット

      // オブザーバの設定値
      const observeConfig = {
        // childList、attributes、characterDataいずれかにtrue必須
        childList: true,       // 対象ノードの子ノード（テキストノードも含む）に対する追加・削除を監視する場合はtrue。
        attributes: true,      // 対象ノードの属性に対する変更を監視する場合はtrue。
        characterData: true,   // 対象ノードのデータに対する変更を監視する場合はtrue。
        subtree: true          // 対象ノードの子孫ノードまで監視する場合はtrue。
      }

      // オブザーバインスタンスを作成
      const observer = new MutationObserver((mutations) => {
        // 更新検出時のコールバック関数
        console.log( "内容項目と説明項目 変更検出個数"+ mutations.length);
        observer.disconnect();                          // 監視の停止
        descriptionRestoreHtmlTag(selectorSet);         // 内容項目・説明項目のMarkdownの書換処理
        observer.observe(observeNode, observeConfig);   // 監視の再開
      });
      // オブザーバの開始
      observer.observe(observeNode,observeConfig);

      return(observer);
    }
  }
```
こちらがコメント欄用の監視

```javascript
  //////////////////////////////////////////////////
  // コメント欄のDOM変更を監視してDOMの上書きを行う
  //////////////////////////////////////////////////
  function setCommentRestoreHtmlTag(){
    // nodeの存在確認
    let node;
    node = document.querySelectorAll('#CommentList');
    if(node.length == 0){
      return;
    }
    let observeNode = node[0]; // 監視対象のNodeをセット

    // オブザーバの設定値
    const observeConfig = {
      // childList、attributes、characterDataいずれかにtrue必須
      childList: true,       // 対象ノードの子ノード（テキストノードも含む）に対する追加・削除を監視する場合はtrue。
      attributes: true,      // 対象ノードの属性に対する変更を監視する場合はtrue。
      characterData: true,   // 対象ノードのデータに対する変更を監視する場合はtrue。
      subtree: true          // 対象ノードの子孫ノードまで監視する場合はtrue。
    }

    // オブザーバインスタンスを作成
    const observer = new MutationObserver((mutations) => {
      // 更新検出時のコールバック関数
      console.log( "コメント欄 変更検出個数"+ mutations.length);
      observer.disconnect();                          // 監視の停止
      commentRestoreHtmlTag();                         // コメント欄のMarkdownの書換処理
      observer.observe(observeNode, observeConfig);   // 監視の再開
    });
    // オブザーバの開始
    observer.observe(observeNode,observeConfig);

    return(observer);
  }
```
そして、ここからが編集画面が開いたら実行される部分です。
初めに「更新」ボタンが押された時の処理を、`$p.events.after_set_Update`に設定します。
`$p.events.after_set_Update`が呼ばれた時点で、プリザンターがDOMの動的変更を完成させていました。以後はユーザが変更しない限りDOMの変更が発生しません。そのため、手動で`commentRestoreHtmlTag`と`descriptionRestoreHtmlTag`を走らせてHtmlタグに変換した後でMutationObserverによる監視を開始しています。
```javascript
  // 「更新」ボタンを押した場合の処理
  $p.events.after_set_Update = function () {
    // この時点でPleasanterがDOMの動的更新が完成している。
    commentRestoreHtmlTag();                        // コメント欄のMarkdownを更新
    Object.keys(selectorSets).forEach(key => {
      descriptionRestoreHtmlTag(selectorSets[key])  // 説明項目と内容項目のMarkdownを更新
    });

    // 「更新」ボタンでDOMが再構築されため再度監視設定
    setDescriptionRestoreHtmlTag();
  }
```
編集画面のロード時の処理は、`DOMContentLoaded`にイベント登録してブラウザがDOMを構築してからスクリプトが開始されるようにしています。この辺の順番関係に疎いので安全策です。
ただし、「更新」ボタンが押された時の処理と違って、この時点では、まだプリザンターの動的なオブジェクトは生成前だったため、ここで監視を登録した後でDOMの変更イベントが発生します。そのためhtmlタグへの変換は変更イベントに任せて手動は行いません。(と言うより、まだ`div.md`が作られて無いため手動実行できません。)
```javascript
  // ブラウザで編集画面を読み込んだ時の処理(開いた時と再読み込みした時)
  document.addEventListener( "DOMContentLoaded", () => {
    // DOM構築完了後に実行
    // この時点でPleasanterのDOMの動的更新は完成していない。
    setCommentRestoreHtmlTag();       // コメント欄のMarkdownを監視設定
    setDescriptionRestoreHtmlTag();   // 説明項目と内容項目のMarkdownを監視設定
  });
}
```
あと、ブラウザが編集画面を読み込んだ時のみ`setCommentRestoreHtmlTag`を走らせているのは、コメントの追加は必ず「更新」ボタンを押すからです。コメント欄の変換処理は`$p.events.after_set_Update`で行えば対応できます。

# 結果

##サンプルMarkDown
~~~Markdown
[md]
# 見出し1
## 見出し2

この部分が<span style="color:red">赤色</span>で表示されます。  
この部分が<font style="color:red">赤色</font>で表示されます。  
この部分が<span style="color:gray">gray色</span>で表示されます  
この部分が<span style="color:silver">silver色</span>で表示されます  
この部分が<span style="color:blue">blue色</span>で表示されます  
この部分が<span style="color:navy">navy色</span>で表示されます  
この部分が<span style="color:teal">teal色</span>で表示されます  
この部分が<span style="color:green">green色</span>で表示されます  
この部分が<span style="color:lime">lime色</span>で表示されます  
この部分が<span style="color:aqua">aqua色</span>で表示されます  
この部分が<span style="color:yellow">yellow色</span>で表示されます  
この部分が<span style="color:red">red色</span>で表示されます  
この部分が<span style="color:fuchsia">fuchsia色</span>で表示されます  
この部分が<span style="color:olive">olive色</span>で表示されます  
この部分が<span style="color:purple">purple色</span>で表示されます  
この部分が<span style="color:maroon">maroon色</span>で表示されます  
この部分が<span style="color:red">赤色</span>で表示されます。この部分が<span style="color:red">赤色</span>で表示されます。この部分が<span style="color:red">赤色</span>で表示されます。


これはインラインコード=> `<span style="color:red">` htmlタグになると困る

```
ここはコードブロック<span style="color:red">赤になると困る</span>htmlタグになると困る
ここはコードブロック<span style="color:red">赤になると困る</span>htmlタグになると困る
ここはコードブロック<span style="color:red">赤になると困る</span>htmlタグになると困る
```   
空行改行する場合は全角スペース2個の後に半角スペース2個  
　　  
　　  
　　  
では、やってられないので`<br>`タグにも対応した。
<br><br><br><br><br><br>
この部分が<span style="color:red">赤色赤色赤色赤色</span>で表示されます  
~~~

## 変換後の画面キャプチャ
![Pleasanter画面1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1542618/1cf73bbb-fcc4-8c02-de57-278d1e46ad1d.png)

# あとがき
プリザンターに、リッチテキストの編集画面が欲しいです。