はじめに
プリザンターは一覧表示で情報を管理する用途では、思い立ってからアプリが動き出すまでが圧倒的短時間で作れるノーコードツールです。多くのツールは白紙のキャンバスを前に途方に暮れるのですが、プリザンターはこの段階が無いのが大きな利点でお気に入りです。
……お気に入りなのですが、ものすごく不満と言いますか、ダメだろコレと思う部分があるんです。それは、文章の文字に「色」を付けることが出来ないところです。
リッチテキストの編集機能とまでは言いませんが、やはり説明文書の注意事項は赤文字にしたいじゃないですか!
方針
本体のソースコードをいじる根性は無いため、ブラウザに表示した後で文字に色を付けたいと思います。プリザンターの独自JavaScriptコードをロードし放題の最高拡張機能を使って、色を付けたい文字を<span>
や<font>
タグで囲えば行けそうです。(ブラウザのDevToosを使って書き換えたら色がつきました)
とは言っても色指定の修飾タグを独自に考えるのは面倒なのでMarkdownの場合のみ変更したいと思います。プリザンターが<span>
を<span>
とエスケープしてくれている部分を元に戻すわけですね。
検証環境
Pleasanter:1.3.20.0
web browser:chrome
ソースコード
サンプルコード
//////////////////////////////////////////////////
// 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(/<span([0-9a-zA-Z=\-:\.,; "'=\t]*?)>(.*?)<\/span>/g, '<span$1>$2</span>');
}
//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元 <font>
//////////////////////////////////////////////////
function restoreHtmlTag_font(contentsText){
// 書式が固定できるなら検索ルールは厳しめにした方が安全
return contentsText.replace(/<font([0-9a-zA-Z=\-:\.,; "'=\t]*?)>(.*?)<\/font>/g, '<font$1>$2</font>');
}
//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元 <code>
//////////////////////////////////////////////////
function restoreHtmlTag_code(contentsText){
// 書式が固定できるなら検索ルールは厳しめにした方が安全
// [`]インラインコードと[```]コードブロックの対応用<code>タグの中
return contentsText.replace(/&lt;([0-9a-zA-Z=\-:\.,; "'=\/\t]*?)&gt;/g, '<$1>');
}
//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元 <br>
//////////////////////////////////////////////////
function restoreHtmlTag_br(contentsText){
// 書式が固定できるなら検索ルールは厳しめにした方が安全だと思う。
// return contentsText.replace(/(?<!<code>)<br>(?!<code>)/g, '<br>');
return contentsText.replace(/(?<!<code>)<br>(?!<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を監視設定
});
}
実行方法
- 「サンプルコード」を開いてコードを表示する。
- ソースコードをコピーする。
- プリザンターの「テーブルの管理」を開く
- 「スクリプト」タブで新規作成
- ソースコードをペーストする。
- 出力先に「編集」を選択する。
解説
スクリプトを作るには、変換する<span>
タグの場所がわからないと話になりませんからDevToolを使って探します。
DevToolでDOMの構造を見ると、内容項目(Body)のMarkdownの場合は#Issues_Body.viewer
の下にdiv.md
ができて、子エレメントの<p>
タグの中に文字列が入ってるようです。
これで検索する場所がわかりました。
はじめに必要な部品を作っていきます。
検索用のセレクタはBody
とDescriptionA〜Z
分を作る必要があるためセレクタを作成する関数をつくります。
//////////////////////////////////////////////////
// 監視に使う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">
タグは<span style="color:red">
に変換しますから、この逆の処理をすればブラウザが認識するHtmlタグの完成です。
しかし、単純に置き換えたらコードブロックやインラインコードの<span style="color:red">
もHtmlタグに戻るため除外する対応が必要です。と思いましたが、プリザンターはコードブロックの中の<span style="color:red">
は&lt;span style="color:red"&gt;
に置き換えていました。別の文字列のため検索に引っかかりません。やったね!
(でも、このままではコードブロックの見た目が悪いので対応しますよ。)
以下の正規表現は「良く解らないけど動作しているからヨシ!」なので適時修正してください。
(正規表現は魔法の呪文として記憶してるので…)
//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元 <span>
//////////////////////////////////////////////////
function restoreHtmlTag_span(contentsText){
// 書式が固定できるなら検索ルールは厳しめにした方が安全
return contentsText.replace(/<span([0-9a-zA-Z=\-:\.,; "'=\t]*?)>(.*?)<\/span>/g, '<span$1>$2</span>');
}
//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元 <font>
//////////////////////////////////////////////////
function restoreHtmlTag_font(contentsText){
// 書式が固定できるなら検索ルールは厳しめにした方が安全
return contentsText.replace(/<font([0-9a-zA-Z=\-:\.,; "'=\t]*?)>(.*?)<\/font>/g, '<font$1>$2</font>');
}
//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元 <code>
//////////////////////////////////////////////////
function restoreHtmlTag_code(contentsText){
// 書式が固定できるなら検索ルールは厳しめにした方が安全
// [`]インラインコードと[```]コードブロックの対応用<code>タグの中
return contentsText.replace(/&lt;([0-9a-zA-Z=\-:\.,; "'=\/\t]*?)&gt;/g, '<$1>');
}
//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元 <br>
//////////////////////////////////////////////////
function restoreHtmlTag_br(contentsText){
// 書式が固定できるなら検索ルールは厳しめにした方が安全だと思う。
return contentsText.replace(/(?<!<code>)<br>(?!<code>)/g, '<br>');
}
Safariは後読み正規表現には対応してませんから、contentsText.replace(/(?<!<code>)<br>(?!<code>)/g, '<br>');
で例外がでます。以下のような感じで逃げるなどしてください。(正規表現は良く解らないから、正規表現から逃げてますね)
let text = contentsText.replace(/<code><br><code>/g, '<code>&lt;br&gt;<code>');
text = text.replace(/<br>/g, '<br>');
text = text.replace(/<code>&lt;br&gt;<code>/g, '<code><br><code>');
実行順番に依存関係がありそうで、複数箇所で使う変換をまとめて関数にします。
//////////////////////////////////////////////////
// MarkDownのHTML Tagエスケープの復元
//////////////////////////////////////////////////
function restoreHtmlTag(contentsText){
return restoreHtmlTag_code(restoreHtmlTag_font(restoreHtmlTag_span(contentsText)));
}
DOMからセレクタでノードを取得して文字列を置換する関数です。ループを回しているだけですね。コメント欄用もつくりました。
JQueryを使ったり使わなかったりしている部分は、selector_html_Tag
の中にJQueryでしか使えないセレクタが入っているからです。
//////////////////////////////////////////////////
// コメント欄の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の変更を監視しています。
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
は便利ですよね。)
//////////////////////////////////////////////////
// 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
に設定します。
$p.events.after_set_Update
が呼ばれた時点で、プリザンターがDOMの動的変更を完成させていました。以後はユーザが変更しない限りDOMの変更が発生しません。そのため、手動でcommentRestoreHtmlTag
とdescriptionRestoreHtmlTag
を走らせてHtmlタグに変換した後でMutationObserverによる監視を開始しています。
// 「更新」ボタンを押した場合の処理
$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
が作られて無いため手動実行できません。)
// ブラウザで編集画面を読み込んだ時の処理(開いた時と再読み込みした時)
document.addEventListener( "DOMContentLoaded", () => {
// DOM構築完了後に実行
// この時点でPleasanterのDOMの動的更新は完成していない。
setCommentRestoreHtmlTag(); // コメント欄のMarkdownを監視設定
setDescriptionRestoreHtmlTag(); // 説明項目と内容項目のMarkdownを監視設定
});
}
あと、ブラウザが編集画面を読み込んだ時のみsetCommentRestoreHtmlTag
を走らせているのは、コメントの追加は必ず「更新」ボタンを押すからです。コメント欄の変換処理は$p.events.after_set_Update
で行えば対応できます。
結果
##サンプル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>で表示されます
変換後の画面キャプチャ
あとがき
プリザンターに、リッチテキストの編集画面が欲しいです。