Markdownを無理やり改変する（OmniMarkupPreviewer向け）

# Markdownでリンクの問題を無理やり解決する

Markdownファイルを作成する時、私はSublimeText3で「OmniMarkupPreviewer」というプラグインを使いながら書いてます。

[OmniMarkupPreviewer](https://github.com/timonwong/OmniMarkupPreviewer)

プレビューができたり、htmlファイルとしてエクスポートもできます。	
メモとしてMarkdownを使うことも多いので重宝していますが、Markdownの標準機能だけだと不便なことも。

そこで、mdファイルにタグを直接書き込むことで便利に使う方法を考えてみました。

## CSSの追加

単純にstyleタグを末尾などに追加します。	
ただ、OmniMarkupPreviewerの場合、bodyに以下のようなタグを設置し流し込む形式になっています。

```html:html(ビルド後)
<body>
	<div class="container">
		<div id="markup">
			<article id="content" class="markdown-body">
				<!-- ここにMarkdownの内容が入る -->
			</article>
		</div>
	</div>
</body>
```

なので、`.markdown-body`クラスを利用してcssを記述すれば安全に上書きすることができます。

```Markdown:mdファイル末尾に挿入
<style>
.markdown-body {
  font-size: 14px;
}
.markdown-body h1 {
  background-color: #558;
  border-radius: 10px;
  color: #fff;
  padding: 10px;
  font-size: 28px;
}
.markdown-body h2 {
  font-size: 24px;
  background-color: #88a;
  color: #fff;
  border-radius: 10px;
  padding: 5px;
  border: none;
}
</style>
```

## リストを利用してツリー構造を表示する

リストの最上層に`<!-- list2tree -->`というコメント文を追加し、普通のリストと分ける準備をします。

```Markdown:markdown
+ <!-- tree -->root/
    - css/
        + common.css
        + sp/
            - common.css
    - img/
        + sp/
            - img_logo_lcc.png
            - icn_chevron_up.png
            - icn_chevron_down.png
    - js/
        + index.js
        + sp/
            - index.js
    - sp/
        + index.html
    - svg/
        + spinner.svg
    - index.html

```

scriptタグでmdファイル末尾にjavascriptを追加します。

```javascript:javascript
// list2tree
Array.prototype.forEach.call(
	document.querySelectorAll('ul > li:first-child'),
	elm => {
		if (/<!-- list2tree -->/.test(elm.innerHTML)) {
			elm.parentNode.classList.add('list2tree');
		}
	}
);
```

styleを追加します。

```css:css
.markdown-body ul.list2tree,
.markdown-body ul.list2tree ul {
	list-style-type: none;
	margin: 0;
	padding: 0;
	font-family: monospace;
}
.markdown-body ul.list2tree {
	display: inline-block;
	background-color: #eee;
	border-radius: 5px;
	padding: 10px;
	margin-bottom: 10px;
}
.markdown-body ul.list2tree ul {
	position: relative;
	margin-left: 10px;
}
.markdown-body ul.list2tree li {
	position: relative;
	margin: 0;
	padding-left: 12px;
	text-decoration:  none;
	font-size: 12px;
	font-weight: normal;
	line-height: 1.2;
}
.markdown-body ul.list2tree > li {
	padding-left: 0;
}
.markdown-body ul.list2tree ul:before {
	content: "";
	display: block;
	position: absolute;
	top: 2px;
	height: 6px;
	width: 0;
	border-left: 1px solid #888;
}
.markdown-body ul.list2tree ul li:not(:last-child):before {
	content: "";
	display: block;
	position: absolute;
	top: 8px;
	left: 0;
	width: 0;
	height: 100%;
	border-left: 1px solid #888;
}
.markdown-body ul.list2tree ul li:after {
	content: "";
	display: block;
	position: absolute;
	top: 7px;
	left:  0;
	width: 8px;
	height: 0;
	border-top: 1px solid #888;
}
```

## リンク関係のカスタマイズ

### リンクを別ページで開く

scriptタグでaタグにtarget属性を追加します。

```javascript:javascript
// リンクを別ウィンドウで開く
Array.prototype.forEach.call(
	document.querySelectorAll('a'),
	elm => {
		elm.target = '_blank';
	}
);
```

### ページ内リンクを作成する

spanタグを直接書きます。

```markdown:markdown
# 見出し１
<span class="jump" data-target="見出し１">見出し１</span>
```

末尾にスクリプトを追加します。

```javascript:javascript
// リンクを別ウィンドウで開く
Array.prototype.forEach.call(
	document.querySelectorAll('a'),
	elm => {
		if (elm.target !== '_self') elm.target = '_blank';
	}
);

// 見出しにidを付与
Array.prototype.forEach.call(
	document.querySelectorAll('h1, h2, h3, h4, h5, h6'),
	elm => {
		elm.id = elm.innerText;
	}
);

// span.jumpをページ内リンクに変更
Array.prototype.forEach.call(
	document.querySelectorAll('span.jump'),
	elm => {
		elm.innerHTML = `<a href="#${elm.dataset.target}" target="_self">${elm.innerText}</a>`;
	}
);
```

jsの処理後のhtmlは以下のようになります。

```html:html
<h1 id="見出し１">見出し１</h1>
<a href="#見出し１" target="_self">見出し１</a>
```

ページ内リンクのアンカーは、`<span class="jump" data-target="見出しテキスト">文字列</span>`を使って指定します。	
全ての見出しに見出しのテキストと同じidを付与し、参照できるように変更、	
また`span.jump`をリンクに書き直しています。

リンクを別ウィンドウで開くスクリプトには条件を追加してページ内リンクが別ウィンドウで開かれないようにしています。

## 表組み（table）関係のカスタマイズ

### 表組みにcolspan、rowspanを設定する

Markdownの記述

```Markdown:markdown
| A | B | C | D |
|: -- |: -- |: -- |: -- |
| <!-- row3 -->A | <!-- col2 -->B | --- | <!-- row2 -->D |
| --- | <!-- row2 -->B | C | --- |
| --- | --- | <!-- col2 -->C | --- |
```

末尾にスクリプトを追加

```javascript:javascript
const TRs = document.getElementsByTagName('tr');
for (let i = 0; i < TRs.length; i +=1) {
	const THTDs = TRs[i].children;
	for (let j = 0; j < THTDs.length; j +=1) {
		const target = THTDs[j];
		if (target.innerHTML.match(/<!-- (col|row)(\d+) -->/)) {
			target[`${RegExp.$1}Span`] = parseInt(RegExp.$2, 10);
		}
		if (target.innerText === '---') {
			target.parentNode.removeChild(target);
			j -= 1;
		}
	}
}
```

jsの処理後のhtmlは以下のようになります。

```html:html
<table>
	<thead>
		<tr>
			<th align="left">A</th>
			<th align="left">B</th>
			<th align="left">C</th>
			<th align="left">D</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td rowspan="3" align="left">A</td>
			<td colspan="2" align="left">B</td>
			<td rowspan="2" align="left">D</td>
		</tr>
		<tr>
			<td rowspan="2" align="left">B</td>
			<td align="left">C</td>
		</tr>
		<tr>
			<td colspan="2" align="left">C</td>
		</tr>
	</tbody>
</table>
```

### 表組みの縦横を入れ替える

```markdown:markdown
| <!-- vh2hv -->--- | --- |
|:--|:--|
| a					| aの内容 |
| b					| bの内容 |
| <!-- row2 -->c 	| cの内容1 |
| ---				| cの内容2 |
```

セルの頭に`<!-- vh2hv -->`をつけトリガーにします。

```javascript:javascript
Array.prototype.forEach.call(
	document.querySelectorAll('table > thead > tr > th:first-child'),
	elm => {
		if (/<!-- vh2hv -->/.test(elm.innerHTML)) {
			elm.parentNode.parentNode.parentNode.classList.add('vh2hv');
		}
	}
);
```

javascriptでtableにクラス名をつけます。

```javascript:javascript
/* vh2hv */
Array.prototype.forEach.call(
	document.querySelectorAll('.vh2hv'),
	elm => {
		elm.removeChild(elm.children[0]);
		let tbody = elm.children[0];
		let tblHtml = [];
		Array.prototype.forEach.call(
			tbody.querySelectorAll(':scope > tr'),
			(tr, trIndex) => {
				Array.prototype.forEach.call(
					tr.querySelectorAll(':scope > td, :scope > th'),
					(td, tdIndex) => {
						if (!(tdIndex in tblHtml)) {
							tblHtml[tdIndex] = [];
						}
						tblHtml[tdIndex][trIndex] = td;
					}
				);
			}
		);
		tblHtml = tblHtml.map((r, rIndex) => r.map(s =>{
			return rIndex ? s.outerHTML : s.outerHTML.replace(/<td/g, '<th').replace(/<\/td>/g, '</th>')
		}).join(''));
		tblHtml = tblHtml.map(r => `<tr>${r}</tr>`).join('');
		tblHtml = tblHtml.replace(/<!-- (row|col)(\d+) -->/g, (r, r1, r2) => {
			const st = (r1 === 'row') ? 'col' : 'row';
			return `<!-- ${st + r2} -->`;
		});
		tbody.innerHTML = tblHtml;
	}
);
```

1. theadタグを削除
2. 置換用のhtml文字列を作成
3. 一列目のtdをthに変換
4. 上記の「表組みにcolspan、rowspanを設定する」と併用するために、colspan、rowspanの表記を入れ替える。
5. tbodyタグ内を置換用文字列に入れ替え。

## mermaid.jsをコードブロックで書けるようにする。

`<div class="mermaid"></div>`タグ内でmermaid用コードを書いても反映はされるのですが、OmniMarkupPreviewerはコードブロックを使った表記に対応してくれないので対応させます。

    ```
    //mermaid
    graph TD;
        A-->B;
        A-->C;
        B-->D;
        C-->D;
    ```

コードブロックの文頭に`//mermaid`を追記してmermaidコードを書きます。

mermaid.js関連の記述と上のコードブロックを処理するjsを書きます。

```html:markdown
<script>
/* mermaid */
window.onload = () => {
  mermaid.initialize({startOnLoad:false})
  const codes = document.querySelectorAll('.codehilite');
  const mermaidRender = () => {
    mermaid.init(undefined);
    mermaid.ganttConfig = {
        numberSectionStyles: 3
    };
  }
  Array.prototype.forEach.call(
    codes,
    (elm, index) => {
      let str = elm.innerHTML;
      if (/\/\/mermaid/.test(str)) {
        elm.classList.add('mermaid');
        elm.classList.remove('codehilite');
        str = str.replace(/(\/\/mermaid|<pre>|<\/pre>)/g, '');
        elm.innerHTML = str;
      }
      if (index === codes.length - 1) {
        mermaidRender();
      }
    }
  );
};
</script>
<script src="https://unpkg.com/mermaid/dist/mermaid.min.js"></script>
```


## 注意

当たり前ですがこれらの方法はgithubやqiitaで使えるものではありません。	
あくまでもOmniMarkupPreviewerなどを使ってhtmlにエクスポートする場合の姑息的な方法です。