はじめに
EvernoteからScrapboxに移行するにあたり、使えなくて困った機能がありました。それは、Scrapboxでは「順序付きリスト」つまり、<ol>
に自動で番号を振ってくれないことです。ということで、ネストに対応した番号を振るUserScriptを作成しました。
使い方
リストに含めたい行を選択して、ポップアップボタンを押して使います。選択行には必ず、行頭を含んでください。そうしないと変なところに番号が出力されます。
例えば、次の文字列に数字を振るとしましょう。
Before
1行目
2行目
3行目
4行目
5行目
6. 6行目
7行目
24. 8行目
-11. 9行目
10行目
12行目
After
1. 1行目
1. 2行目
2. 3行目
2. 4行目
1. 5行目
6. 6行目
7. 7行目
8. 8行目
-11. 9行目
-10. 10行目
2. 12行目
<ol>
におけるstart
属性にも対応しています。行頭(今回は6, 9行目)に"数字+ドット"の形式で数値を入力しておくと、その番号から連番を始めてくれます。
ただし、同じ数のインデントがついた最初の行のみを参照します。例えば、8行目には数値が入っていますが、すでに同じ数のインデントを持つ行が存在しているため、「24」という数字は無視され、代わりに「6+2=8」が代入されます。
空白行は単純に番号をつけずにスキップします。くわえて、値はマイナスにも対応していますが、Scrapboxの表示としては数字つきリストではなく、普通のリスト扱いになるようです。
ソースコード
scrapbox.PopupMenu.addButton({
title: 'Ordered list',
onClick: text => {
let result = formatOrderList(textToNestedList(text));
if(/^\S/gm.test(result)) {// インデントが行頭にない行が存在する場合は追加する
result = result.replace(/^/gm, "\t");
}
return result;
}
});
function textToNestedList(text) {
const reg = /(^\s*)(.*)/;
return text.split("\n").reduce((result, line) => {
const level = line.replace(reg, "$1").length;
const content = line.replace(reg, "$2");
let parent = result;
for (let i = 0; i < level; i++) {
if (parent.length === 0 || !Array.isArray(parent[parent.length - 1])) parent.push([]);
parent = parent[parent.length - 1];
}
parent.push(content);
return result;
}, []);
}
function formatOrderList(list, indent = 0) {
const blank = "\t".repeat(indent);
const firstLine = list.find(value => !Array.isArray(value));
const reg = /(^-?\d+)(\.\s*)/;
const start = reg.test(firstLine) ? firstLine.split(".")[0] : 1;
let index = 0;
return list.map(item => {
if (Array.isArray(item)) {
return this.formatOrderList(item, indent + 1);
}
if(item.length === 0) {
return blank;
}
return blank + `${Number(start) + index++}. ` + item.replace(reg, "");
}).join("\n");
}
textToNestedList()
はscrapbox.PopupMenu.addButton
にインライン化して平気ですが、formatOrderList()
はインライン化はできません。なぜなら、値を入れ子にするために循環参照をしているからです。
テストはザックリしただけなので、バグがあったら教えてください。
参考資料
ネストに対応していない順序付きリストでいいなら、こちらの方が簡単。