Backlog 記法からマークダウン記法へ変換するブックマークレットを試してみた

Backlog 記法からマークダウン記法に変換したいときに。

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

コンバンハ、千葉(幸)です。

Backlog 記法で書かれた Wiki をマークダウン記法に変換したい時があります。以下で便利なブックマークレットが紹介されていたため試してみます。

ブックマークレットを登録する

上記の記事で紹介されている以下ページから 1 行丸ごとコピーします。

ブラウザからブックマークの作成を試み、URL 部にコピーした内容を入力して保存します。

bookmarklet

通常のブックマークと同じように、指定した箇所に作成されていればOKです。

bookmarklet-6811555

ブックマークレットの処理の内容

URL デコード + 整形した内容が以下です。

javascript: ((e, t) => {
    "use strict";
    if (!e) throw new TypeError("テキストエリアが見つかりませんでした。");
    e.value = (e => {
        const t = [],
            n = [],
            r = [],
            a = e => {
                return [{
                    pattern: /<(.*?)>/g,
                    replacement: (e, t) => `&lt;${t}&gt;`
                }, {
                    pattern: /</g,
                    replacement: (e, t) => "&lt;"
                }, {
                    pattern: /''(.*?)''/g,
                    replacement: (e, t) => ` **${t.trim()}** `
                }, {
                    pattern: /'(.*?)'/g,
                    replacement: (e, t) => ` *${t.trim()}* `
                }, {
                    pattern: /%%(.*?)%%/g,
                    replacement: (e, t) => ` ~~${t.trim()}~~ `
                }, {
                    pattern: /\[\[(.*?)[:>](.*?)\]\]/g,
                    replacement: (e, t, n) => `[${t.trim()}](${n})`
                }, {
                    pattern: /&color\((.*?)\)(\s+)?{(.*?)}/gi,
                    replacement: (e, t, n, r) => `<span style="color: ${t};">${r}</span>`
                }, {
                    pattern: /#image\((.*?)\)/,
                    replacement: (e, t) => `![${t}]`
                }, {
                    pattern: /#thumbnail\((.*?)\)/,
                    replacement: (e, t) => `![${t}]`
                }, {
                    pattern: /#attach\((.*?):(.*?)\)/,
                    replacement: (e, t, n) => `[${t}][${n}]`
                }, {
                    pattern: /  /,
                    replacement: " "
                }, {
                    pattern: /&/,
                    replacement: "&amp;"
                }].forEach(({
                    pattern: t,
                    replacement: n
                }) => {
                    e = e.replace(t, n)
                }), e
            },
            p = [{
                pattern: /\n\r/g,
                replacement: "\n"
            }, {
                pattern: /\r/g,
                replacement: "\n"
            }, {
                pattern: /^(\*+)(.*)$/gm,
                replacement: (e, t, n) => `\n${t.replace(/\*/g,"#")} ${a(n.trim())}\n`
            }, {
                pattern: /\n\|([\s|\S]*)\|\n(?!\|)/g,
                replacement: (e, t) => {
                    if (-1 === t.indexOf("|h\n")) {
                        let e = 0,
                            n = t.split("\n")[0].split("|").length,
                            r = "";
                        for (; e < n; e++) r += "|:--";
                        return `\n${r+="|"}\n|${t}|\n`
                    }
                    return `\n|${t}|\n`
                }
            }, {
                pattern: /^\|(.*)\|(\s?)$/gm,
                replacement: (e, t, n) =>
                    `|${t=(t=(t=(t=(t=(t=t.replace(/\|~/g,"|")).replace(/^~/g,"")).replace(/\|\|/g,"| |")).replace(/^\|/g," |")).replace(/^\|/g," |")).replace(/\|$/g,"| ")}|${n}`
            }, {
                pattern: /^\|(.*)\|h\s?$/gm,
                replacement: (e, t) => {
                    let n = "",
                        r = t.split("|"),
                        a = 0,
                        p = r.length;
                    for (; a < p; a++) n += "|:--";
                    return n += "|",
                        `\n|${t=(t=(t=(t=(t=t.replace(/\|~/g,"|")).replace(/^~/g,"")).replace(/\|\|/g,"| |")).replace(/^\|/g," |")).replace(/\|$/g,"| ")}|\n${n}`
                }
            }, {
                pattern: /\n\|([\s|\S]*?)\|\n([^|])/g,
                replacement: (e, t, n) => `\n|${a(t)}|\n\n${n}`
            }, {
                pattern: /\n\|([\s|\S]*?)\|\n(?!\|)/g,
                replacement: (e, t) => `\n\n|${t}|\n\n`
            }, {
                pattern: /\n\+([\s|\S]*?)\n\n/g,
                replacement: (e, t) => `\n+${t}\n\n\n`
            }, {
                pattern: /\n\+([\s|\S]*?)\n\n/g,
                replacement: (e, t) => {
                    let n = ""; {
                        const e = [];
                        let r = 0;
                        (t = (t = (t = "\n+" + t.trim()).replace(/^(\++)(.*)$/gm, (e, t, n) =>
                            `${t} ${n.trim()}`)).trim()).split("\n").forEach(t => {
                            const p = t.split(" ")[0].length;
                            p < r && (e[r] = 0), e[r = p] = e[r] ? e[r] + 1 : 1, n += a(
                                t.replace("+ ", e[r] + ". ")) + "\n"
                        })
                    }
                    return `\n${t=n.replace(/^(\++)(.*)/gm,(e,t,n)=>{const r=t.length;let a=0,p="";for(;a<r;a++)p+="    ";return p+n})}\n`
                }
            }, {
                pattern: /^(-+)(.*)$/gm,
                replacement: (e, t, n) => {
                    let r = 0,
                        p = t.length - 1,
                        l = "";
                    if (!n) return t;
                    for (; r < p; r++) l += "    ";
                    return l += "-", n = a(n).trim(), `${l} ${n}`
                }
            }, {
                pattern: /&br;/g,
                replacement: " <br>"
            }];
        e = (e = (e = (e = (e = (e = "\n" + e + "\n\n").replace(/^#contents$/gm, "[toc]\n")).replace(
            /\n{code}([\s|\S]*?){\/code}\n/g, (e, n) => (t.push(n),
                `\n{{CODE_REPACE_BACKLOG_TO_MARKDOWN-${t.length-1}}}\n`))).replace(
            /\n{quote}([\s|\S]*?){\/quote}\n/g, (e, t) => (n.push(t),
                `\n{{QUOTE_REPACE_BACKLOG_TO_MARKDOWN-${n.length-1}}}\n`))).replace(/^.*$/gm, (
            () => {
                const e = /^(?![*\|\-\+\s>)`])(.*)$/;
                return t => t && e.test(t) && !t.startsWith(
                    "{{CODE_REPACE_BACKLOG_TO_MARKDOWN") && !t.startsWith(
                    "{{QUOTE_REPACE_BACKLOG_TO_MARKDOWN") ? (r.push(t),
                    `{{PARAGRAPHS_REPACE_BACKLOG_TO_MARKDOWN-${r.length-1}}}`) : t
            })())).replace(/\n{{PARAGRAPHS_REPACE_BACKLOG_TO_MARKDOWN-.*?}}\n(?!{{)/g, e =>
            `${e}\n`), p.forEach(({
            pattern: t,
            replacement: n
        }) => {
            e = e.replace(t, n)
        });
        for (;
            /\n\n\n/g.test(e);) e = e.replace("\n\n\n", "\n\n");
        return (e = (e = (e = e.replace(/{{CODE_REPACE_BACKLOG_TO_MARKDOWN-(.*?)}}/g, (e, n) => t[
                Number(n)].trim() ? "\n```\n" + t[Number(n)].trim() + "\n```\n" :
            "\n```\n```\n")).replace(/{{QUOTE_REPACE_BACKLOG_TO_MARKDOWN-(.*?)}}/g, (e, t) => {
            let r = n[Number(t)].trim();
            return "\n> " + (r = r.split("\n").join("\n> ")) + "\n"
        })).replace(/{{PARAGRAPHS_REPACE_BACKLOG_TO_MARKDOWN-(.*?)}}/g, (e, t) => {
            let n = r[Number(t)].trim();
            return n = a(n)
        })).trim()
    })(e.value)
})(document.querySelector('textarea#descriptionTextArea, textarea[id="page.content"]'));

処理の内容は以下で取り上げられているので気になる方はチェックするとよいでしょう。

Backlog 記法からマークダウン記法に変換する

登録したブックマークレットを試してみます。以下の Backlog 記法で書かれた Wiki を変換してみます。

Backlog_Wiki

こちらは以下の内容で記されています。

Backlog記法

* 見出し1
** 見出し2
*** 見出し3

- リスト
-- リスト
-- リスト
--- リスト
- リスト

|header1|header2|header3|h
|col1|col2|col3|
|col1|col2|col3|

{code}
コードの内容
{/code}

''太字'' 
'''斜体''' 
%%取り消し%% 
[[リンク:http://example.com/]]

Wiki の編集モードに入り、登録したブックマークレットを実行します。

Bookmarklet_convert

「ページの内容」部が変換されます。このブックマークレットが行ってくれるのはここまでです。そのまま保存する、変換後のテキストをコピーして編集をキャンセルする、などお使いの環境にあわせて取り扱ってください。

今回のテキストでは以下のように変換されました。

変換後のテキスト

# 見出し1

## 見出し2

### 見出し3

- リスト
    - リスト
    - リスト
        - リスト
- リスト

|header1|header2|header3|
|:--|:--|:--|
|col1|col2|col3|
|col1|col2|col3|


```
コードの内容
```


 **太字** 
 ** *斜体*** 
 ~~取り消し~~ 
[リンク](http://example.com/)

マークダウン形式で表示される Wiki で確認すると、以下のように見えます。斜体の部分だけはちょっと惜しいことになっていますが、ほぼ問題なく変換されています。

終わりに

Backlog 記法からマークダウン記法に変換するブックマークレットを試してみました。やりたいことは大体満たせるので大変助かります。

冒頭の記事でも書かれているように、以下注意点を考慮の上ご使用ください。

  • ご利用は自己責任でお願いいたします
  • ミスったらまずいページや課題詳細は必ずプレビューを行ってください
  • Backlog記法とMarkdownはできることに違いがありますから、そこも含めて検討したほうがが良さそうです
    • たとえば、BacklogでのMarkdownはHTMLを無視されるので、文字色が変更できません
      • 非アクセシブルではありますが、文字色が重要な現場では慎重にご検討ください
  • BacklogのMarkdownならではの記法やルールも存在するため、本コードで変換したものがMarkdownサポートをしているGitHubなどの各種サービスでもそのまま使えるわけではないことにご注意ください

以上、 チバユキ (@batchicchi) がお送りしました。

おまけ

Typora というマークダウンエディタを使用している場合、Wiki のテキスト(編集画面ではなく表示されているもの)をコピーして Typora にペーストするだけで勝手にマークダウンに変換してくれます。

2021年に有償化されましたが、いろいろ便利なので気に入って使っています。他のエディタでも似たようなことをやってくれるかも知れません。

関連