BacklogのWIKIページにMermaidで図を描くツール

# BacklogのWIKIページにMermaidで図を描くツール

現時点において、BacklogのWIKIはMermaidで図が書かません。
なんとかならないのかと考えていたらいいアイディアが浮かいました。
というわけでツール作ってみました。


## ツールの説明

* HTMLページ1枚です。ローカルのブラウザで開いて起動します。
* HTMLファイルを作成し、コピペで作成してください。
* javascriptでbacklogのAPIを呼んでいます。そのため、APIキーを個人設定から取得してください。
* このツールは、WIKIのページIDからbacklog APIを使用して、記述内容を取得します。
* Mermaidの記述した部分を、切り出して、MermaidJSでSVGを描写しています。
* SVGをWIKIページに添付します。
* 添付したSVGを画像として、ページに埋め込みます。
* そしてWIKIページを上書きます。
* 自分で変換、貼り付ける手間がなくなります。
* 修正して、再度ツール実行すると、新しいイメージが表示されます。
* コードはとりあえずのコードなので、エラー処理、テストなど不十分です。

## ツールの使い方

* APIキーをbacklogの個人設定ページから取得します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1371938/e7e5020e-1515-5b6d-251f-186a3b813862.png)
* URLはbacklogのアクセスするURLを取得します。(API Keyでのアクセスを今回行っています。その他の認証の場合は自分で対応お願いします。)
* WIKIページを表示したURL`https://aaaaaa.backlog.jp/alias/wiki/1075999999`の後ろの数字がIDになります。
* この3つを取得し、ボタンを押すと、WIKIページにMermaidで描写したイメージが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1371938/1aa2f36b-8e59-727d-4206-789b043099d2.png)
* 表示されたページ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1371938/6368ab49-3677-e374-a9df-70fc68e66609.png)


## その他

* Mermaid記述されたテキストはそのまま表示されます。消したがったが、コメントアウトできず。しょうがない。
* `<！-- -->`でコメントアウトできるが、Mermaidの内容でコメントが解除されてしまう。
* とりあえずWIKI書きながら図が出るようになったので、
* 気になる人は運用でカバーしてください。


## 実際のHTML

```html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>Mermaid SVG API Post</title>
    <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
</head>

<body>
    <div>
        <div>url:<input id="url" type="text" value="" placeholder="https://xxxxx.backlog.jp" />
        </div>
        <div>backlogにアクセスするURL</div>
        <div>APIキー:<input id="token" type="text"
                value=""
                placeholder="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" /></div>
        <div>個人設定のAPIから取得したAPIキー</div>
        <div>wiki ID:<input id="wikiid" type="wikiid" value="" placeholder="9999999999" /></div>
        <div>backlogのwikiページ開いたときの数字</div>
        <div>
            <h3>使い方</h3>
            backlogwikiページに
            
<pre>```Mermaid
flowchart LR
A[Hard edge] -->|Link text| B(Round edge)
B --> C{Decision}
C -->|One| D[Result one]
C -->|Two| E[Result two]
```</pre>
            のように記述して保存してください。フォームに値を入れて、ボタンを押す。<br>
            ```Mermaidを検索して、SVGを生成しています。表記が違うと、動きません。
        </div>
    </div>
    <div>
        <button id="Mermaid">Mermaid svg 添付</button>
    </div>
    <div id="mermaid-container"></div>

    <script>
        // Mermaidの初期化
        mermaid.initialize({
            startOnLoad: false,
            theme: 'default'
        });


        async function sendMermaid() {
            const wiki = await getWiki();
            console.log(wiki);
            const lines = wiki.content.split(/\r?\n/);
            console.log(lines);
            let inMer = false;
            let cnt = 1;
            let mercont = [];
            let updateCont = [];
            let fileName = '';
            let imageLink = '';
            const now = new Date();
            const formattedDate = `${now.getFullYear()}${(now.getMonth() + 1).toString().padStart(2, '0')}${now.getDate().toString().padStart(2, '0')}${now.getHours().toString().padStart(2, '0')}${now.getMinutes().toString().padStart(2, '0')}${now.getSeconds().toString().padStart(2, '0')}`;
            for (let index = 0; index < lines.length; index++) {
                const line = lines[index];
                updateCont.push(line);
                if (inMer) {
                    if (line === '```') {
                        // 終了
                        inMer = false;
                        console.log(mercont.join('\n'));
                        // 図描画
                        const svg = await renderMermaidDiagram(mercont.join('\n'));
                        // backlogに添付
                        const id = await postSvgToApi(svg, fileName);
                        // 画像リンク
                        imageLink = `![image](/downloadWikiAttachment/${id}/${fileName})`;
                        if (/Mermaid[0-9]+-[0-9]+[.]svg/.test(lines[index + 1])) {
                            // 置き換え
                            lines[index + 1] = imageLink;
                        } else {
                            // 新規追加
                            updateCont.push(imageLink);
                        }
                        // 次の番号
                        cnt++;
                    } else {
                        mercont.push(line);
                    }
                } else if (line === '```Mermaid') {
                    // Mermaid開始
                    fileName = `Mermaid${cnt}-${formattedDate}.svg`;
                    mercont = [];
                    inMer = true;
                }

            }
            if (cnt != 1) {
                updateWiki(wiki.name, updateCont.join('\r\n'));
            }
        }

        // Mermaid図をレンダリング
        async function renderMermaidDiagram(mercont) {
            try {
                const { svg } = await mermaid.render('mermaidDiagram', mercont);
                document.getElementById('mermaid-container').innerHTML = svg;
                return svg;
            } catch (error) {
                console.error('Mermaid描画エラー:', error);
            }
        }


        async function getWiki() {
            // https://developer.nulab.com/ja/docs/backlog/api/2/count-wiki-page/
            const url = document.getElementById('url').value;
            const token = document.getElementById('token').value;
            const wikiid = document.getElementById('wikiid').value;
            const wiki = await fetch(`${url}/api/v2/wikis/${wikiid}?apiKey=${token}`);
            return wiki.json();
        }

        async function updateWiki(name, content) {
            // https://developer.nulab.com/ja/docs/backlog/api/2/update-wiki-page/
            const url = document.getElementById('url').value;
            const token = document.getElementById('token').value;
            const wikiid = document.getElementById('wikiid').value;

            const formData = new FormData();
            formData.append('name', name);
            formData.append('content', content);
            formData.append('mailNotify', false);
            const wiki = await fetch(`${url}/api/v2/wikis/${wikiid}?apiKey=${token}`, {
                method: 'PATCH',
                body: formData,
            });
            return wiki.json();
        }

        // SVGをAPIにPOST
        async function postSvgToApi(svgContent, fileName) {

            // https://developer.nulab.com/ja/docs/backlog/api/2/post-attachment-file/

            // 添付ファイルを登録
            const formData = new FormData();
            formData.append('file', new Blob([svgContent], { type: 'image/svg+xml' }), fileName);

            const url = document.getElementById('url').value;
            const token = document.getElementById('token').value;
            const wikiid = document.getElementById('wikiid').value;
            const response1 = await fetch(`${url}/api/v2/space/attachment?apiKey=${token}`, {
                method: 'POST',
                body: formData
            });

            if (!response1.ok) {
                throw new Error(`HTTPエラー! ステータス: ${response.status}`);
            }

            const result = await response1.json();
            console.log('APIレスポンス:', result);

            // https://developer.nulab.com/ja/docs/backlog/api/2/attach-file-to-wiki/
            // WIKIに添付ファイルを追加
            const response2 = await fetch(`${url}/api/v2/wikis/${wikiid}/attachments?apiKey=${token}&attachmentId[]=${result.id}`, {
                method: 'POST',
                body: {}
            });
            if (!response2.ok) {
                throw new Error(`HTTPエラー! ステータス: ${response.status}`);
            }
            const result2 = await response2.json();
            console.log('APIレスポンス:', result2);
            return result2[0].id;
        }

        // ボタンにイベントリスナーを追加
        document.getElementById('Mermaid').addEventListener('click', sendMermaid);

    </script>
</body>

</html>
```