scrapbox:: ← → markdown変換(改訂版)
scrapbox(Cosense)の表記が独特で,markdownに変換したい要望はたくさんでていて,実装例がいくつもある.
その中から紹介し,カスタマイズした記法にも対応する方策を考えた.
◆bookmarklet版
以前紹介した.
▼デメリット▼
bookmarkletは登録が少し面倒なのと,一行プログラムなので,メンテがやりにくい.
◆webアプリ版
scrapboxとmarkdownの双方向変換をしてくれるwebサイト.
これは便利.便利以外のなにものでもない.ありがとう.
▼デメリット▼
作り付けなので中身は当然見えない.こちらで勝手にカスタマイズした記法には当然対応していない.
★カスタマイズに対応するには
自前のスタイルファイルやcssでカスタマイズしている場合は,後から別に編集が必要.
おいらはvimscriptで,vim中で処理している.
↓↓ のscrapbox拡張版のscrpit.js を改造すれば,カスタマイズにも対応できる.
◆scrapbox拡張版
ついに見つけた.scrapboxの script.jsに,追加するタイプ.
★script.jsの登録方法は:
プロジェクトのowner名のノートに,
"code:script.js"として,コードを追加する.
独自の拡張をしていても,書き換えれば対応可能だよな.これで.
↓↓ に自分用にカスタマイズしたバージョンをお送りする.
code:cosense2markdown
scrapbox.PopupMenu.addButton({
title: 'Copy markdown',
onClick: sb2md,
});
scrapbox.PageMenu.addItem({
title: 'Copy this page as markdown',
onClick: () => {
const text = scrapbox.Page.lines.map(l => l.text).join('\n');
copyText(sb2md(text));
},
});
function sb2md(text) {
// code block
const escapeCodeBlocks = s => s.replace(
/^\s+code:(.+)$((\n^ \t.*$)+)/mg, (_, p1, p2) =>
'' + p1 + p2.replace(/^[ \t]/mg, '').replace(/\r|\n|\r\n/g, '+++') + '+++'
);
const unescapeCodeBlocks = s => s.replace(/\+{3}/g, '\n');
const replaceLine = line =>
/^`{3}/.test(line) ? line :
// Heading処理
line
// 引用符">"をタイトル用に乱用しているので"> [" → "["に変換
.replace(/>\s+\[/, '[')
// マーカーは Heading3に変換
.replace(/^\[\[([^\\]+)\]\]$/, '### $1') // 強調 * は Heading2 に変換
.replace(/^\[\*\s+(\S[^\\]*)\]$/, '## $1') // 強調 **と***は Heading1 に変換
.replace(/^\[\*\*\s+(\S[^\\]*)\]$/, '# $1') .replace(/^\[\*\*\*\s+(\S[^\\]*)\]$/, '# $1') // anchor link
.replace(/\[(https?:\/\/[^ \\]+) +([^\\]+)\]/g, '$2($1)') .replace(/\[ *(((?!http)[^ \\]+ +)*(?!http)[^ \\]+) +(https?:\/\/[^ \\]+)\]/g, '$1($3)') // bold text
// 強調系(*,**,!等)はすべて bold 一択変換
// ※この処理はHeading処理の後に置くべし
.replace(/\[\[([^\\]+)\]\]/g, '**$1**') .replace(/\[\*+\s+([^\\]+)\]/g, '**$1**') .replace(/\[\!\s+([^\\]+)\]/g, '**$1**') // italic text
.replace(/\[\/\s+([^\\]+)\]/g, '*$1*') // strike text
.replace(/\[-\s+([^\\]+)\]/g, '~~$1~~') // センタリングや右揃えはキャンセル
.replace(/\<>]\s+([^\[\+)\]/g, '$1')
// image block:行頭になくても処理
// unordered list
.replace(/^\s(\S.*)$/, '- $1')
.replace(/^\s{2}(\S.*)$/, ' - $1')
.replace(/^\s{3}(\S.*)$/, ' - $1')
;
text = escapeCodeBlocks(text)
.split(/\r|\n|\r\n/);
// first line is level 1 heading
text = (lines => [lines0.replace(/^(.+)$/, '# $1')].concat(lines.slice(1)))(text) .map(replaceLine)
.join('\n');
return unescapeCodeBlocks(text);
}
function copyText(text) {
const pre = document.createElement('pre');
pre.innerText = text;
document.body.appendChild(pre);
document.getSelection().selectAllChildren(pre);
document.execCommand('copy');
document.body.removeChild(pre);
}
以上.