Obsidian Outliner を使って、区切られたセクションを出力する Templater スクリプト「SectionOut」
以前書いた記事で、Obsidian Outliner プラグインで書いたものを文章化するためのスクリプト「HeadOut」を取り上げました。
このときの課題は、スクリプトの処理範囲がそのファイル全体に及ぶために、フロントマターや別のリスト、タスクまで変換してしまうことでした。とくにフロントマターに影響が及ぶのはめちゃくちゃ面倒です。文章を書いてるのになんでフロントマターまで変換された状態で見なきゃいけないんだ!
今回はそんな厄介な課題を解決できる新たなスクリプト「SectionOut」と、その補助テンプレート「SectionSet」をご紹介します。
セクションをセットする「SectionSet」
今回のスクリプトを考えるにあたって、まず考えたのが範囲選択です。範囲を考えなければ処理対象を限定することもできないためです。
templater スクリプトは javascript で記述します。そのため、処理範囲も javascript のルールに従って処理すれば考えることができます。
今回、処理範囲については次の記事を参考にしました。
カーソルのある位置の前後に区切りの記号があれば、それを識別して範囲選択するというやり方を、もうそのまま踏襲しました。こうすることで、複数の出力範囲があっても、スクリプトの実行をカーソル周辺の範囲に限定することができます。
区切りの記号については別に見出しを示す「#」でも良かったんですけど、今回はそれだと面白くないので、「===」にしました。なんか「#」だと区切り感がないし変換後の見出し記号とかぶる、けれど半角イコールを並べると色々面倒だな~ということで、日本語文章だし全角イコールでええやんという安直な理由です。
言い換えれば、次の写真のように「===から始めて、===で終わるリストが対象」です。
なお、区切り記号は「===」ですが、適宜変更できます。
で、その範囲を設定するにあたって、いちいちこんな記号を書いていては面倒すぎるぞ、と。そこで、今回はその区切り記号のセットを出力するテンプレートも作りました。
以後、区切り記号で区切られた区画のことを「セクション」と説明します。
===
- <% tp.file.cursor() %>
===
これを挿入すると、自動的にセクションが区切られ、区切り記号を入力することなく、すぐに文章を書き始めることができます。
また、templater の tp.file.cursor 機能を使うことで、自動的にリストの位置へとカーソルが転移します。
セクションを出力する「SectionOut」
ここからが本題。セクションをセットし書き進め、これを出力しようとします。
今までの「HeadOut」では、文章を処理可能なリストとして変換する際に「tp.file.content.split('\n')」を使っていたのですが、今回はrangeを先に指定しているので、この方法は使えません。困ったので ChatGPT に聞いてみたら、ふつうにさっき指定した範囲(ここでは「str」)を split すればいいだけでした。拍子抜けです。
あとはまったく HeadOut と同じスクリプトを実行して終わり。ただし、スクリプトはセクション内にカーソルがある状態で実行してくださいね。
HeadOutとの細かな変更点
HeadOutから変えた部分としては、
フロントマターがあっても使えるようになった(ここ重要)
行頭の「+」記号が不要に
先頭行を見出し1、そのほかの1段目を見出し2として変換する形に変更
このあたりかな?
使いやすくなったんじゃないでしょうか。
今回のスクリプト
<%*
// エディタと現在のカーソルの位置を取得
const editor = this.app.workspace.activeLeaf.view.editor;
const curCur = editor.getCursor();
// カーソルのある位置から、まず先頭位置を捕捉
let str = editor.getRange(editor.offsetToPos(0), curCur);
const preSec = str.match(/(.*===\s).*?$/s);
// セクションの終了位置を捕捉
str = editor.getRange(curCur, editor.offsetToPos(editor.getValue().length));
const surSec = str.match(/(.*?)===\s.*$/s);
// オフセットがない場合はこのページのすべてを取得する
let startOffset = 0;
let endOffset = editor.getValue().length;
if(preSec) { startOffset = preSec[1].length; }
if(surSec) { endOffset = editor.posToOffset(curCur)+surSec[1].length; }
// 範囲確定
str = editor.getRange(editor.offsetToPos(startOffset), editor.offsetToPos(endOffset));
// ここからはHeadOutとほぼ一緒
const lines = str.split('\n');
let result = [];
lines.forEach((line, index) => {
if (index === 0) {
result.push(`# ${line.trim().slice(2)}`);
} else {
if (line.startsWith(` - 「`)){
result.push(`\n${line.trim().slice(2)}`);
} else if (line.startsWith(`- `)){
result.push(`\n## ${line.trim().slice(2)}`);
} else if (line.startsWith(` - `)){
result.push(`\n ${line.trim().slice(2)}`);
} else if (line.startsWith(` - `)) {
result.push(`${line.trim().slice(2)}`);
}
}
});
const finalResult = result.join('');
navigator.clipboard.writeText(finalResult);
new Notice("セクションのテキストを変換してコピーしました");
-%>
これで、安心してどこでもアウトライナーを文章化することができるようになりましたとさ。めでたしめでたし。
なお例によって、javascript 初心者のため思わぬバグがあるかもしれません。もし何か見つかればコメント等でご指摘いただけると幸いです。
ついでに
選択行をセクション化するスクリプト「SectionMark」も作ってみました。
<%*
const editor = app.workspace.activeLeaf.view.editor;
let stext = app.workspace.activeLeaf.view.editor.getSelection();
let lines = stext.split('\n').map(line => '- ' + line);
let symbolBefore = '===';
let symbolAfter = '===';
let newTextArray = [symbolBefore, lines.join('\n'), symbolAfter];
let newText = newTextArray.join('\n');
editor.replaceSelection(newText);
-%>
平文でメモを書き、セクション化する流れ。元々がリストの場合は別に記号は手打ちでいいやっていう割り切りも大切です。
そこまでしてアウトライナーで書きたいのか?
わからない。
アウトライナーには構造化という強みがあるけれど、散文を書くなら別にこの仕組みを使わなくてもいいという気はする。
まとめ
他力本願に ChatGPT 、もうなんでもアリな気がしてきた。
今回、一番驚いたのは、ChatGPTのObsidian Templater に関する理解度の高さ。このスクリプトとこのスクリプトをPPAPしたいんやけど(意訳)って言ったら、完璧なスクリプトが出てきて、コピペでほぼそのまま使える状態に仕上がっていました。これは人間滅ぼされる日が近いかもしれん。
なお、今回のスクリプトについてですが、実は templater 特有の機能を使っていません。あれ? ひょっとしてプラグイン化に一歩前進した?(してない)
あと先駆者の記事をよく読むと、やりたいことの6割くらいはほぼ実現されていました。私は車輪の再発明をしている。
追記(2024 05/06)
改良版を出しました。こっちのほうが使いやすいと思います。
この記事が気に入ったらサポートをしてみませんか?