はじめに

タイトル通り、ObsidianのDataviewにページング機能を追加する。
Dataviewのdefaultの機能ではクエリに引っかかったノート一覧を全て表示するか、limitを設定して上位n個を表示するしかできない。
全て表示だとノートが多い時にとても重くなるし、limitだと後半のノートは表示できなくなるという問題がある。

issuesにも上がっているが、随分前からあるissueのようで対応には期待できない。Dataviewの後継プラグインのDatacoreが開発中で、そちらにはページング機能が搭載されるらしいが、まだリリースはされていない。

調べると、redditにMeta Bindを使用した解決策が載っていたので導入することにした。

導入手順

引用元のコードは下記

const pageNum = dv.current().page;
const start = 10 * (pageNum - 1);
const end = 10 * pageNum;
const results = dv.pages();
const totalPages = Math.ceil(results.length / 10);
const options = results.slice(0, totalPages).map((_, i) => `option(${i + 1})`).join(',');
const selector = `\`INPUT[inlineSelect(${options}):page]\``;
dv.span(`Page ${selector} out of *${totalPages}*
`);
const rows = results.slice(start, end).map(p => [p.file.link, p.file.ctime]);
const cols = ['File', 'Created'];
dv.table(cols, rows)

なるほど。シンプルで良さそう。

これを📘Obsidian WebClipperを導入してみたで作成したテーブルに追加したいが、Dataviewjsが必須のよう。
今回の様に書き方を変えたいだけの場合は生成AIで8割行けるので、前記事のクエリをchatGPTに変換してもらい、それを修正したうえでページングロジックを追加した。

// フロントマターの設定
const search = dv.current().search || ""; // 検索クエリ(デフォルトは空文字)
const perPage = dv.current().perPage || 10; //1ページの件数(デフォルトは10)
 
// データを取得し、条件でフィルタリング
const results = dv.pages('"Clippings"')
    .where(p => {
        // 検索条件がない場合は全て通過
        if (!search) return true;
        // フィルタ対象のフィールドを取得
        const fields = [p.title, p.description, p.author].filter(Boolean); // nullやundefinedを除外
        // icontains相当: 小文字で部分一致を検索
        return fields.some(field =>
            typeof field === "string" && field.toLowerCase().includes(search.toLowerCase())
        );
    })
    .sort(p => p.file.ctime, "desc") // 作成日時で降順ソート
 
// ページングロジックを追加
const pageNum = dv.current().page;
const start = perPage * (pageNum - 1);
const end = perPage * pageNum;
const totalPages = Math.ceil(results.length / perPage);
const options = results.slice(0, totalPages).map((_, i) => `option(${i + 1})`).join(',');
const selector = `\`INPUT[inlineSelect(${options}):page]\``;
dv.span(`Page ${selector} out of ${totalPages}
`);
 
// 結果をテーブル形式で表示
dv.table(
    ["Time", "Image", "Link"], // テーブルの列ヘッダー
    results.slice(start, end).map(p => [
        moment(p.file.ctime.toString()).format("YYYY-MM-DD HH:mm:ss"), // 作成日時をフォーマット
        p.image
            ? `![Image](${p.image})` // 画像がある場合はMarkdown形式で表示
            : `![Placeholder](https://picsum.photos/seed/${p.file.name}/160/90)`, // プレースホルダー画像
        `[${p.title || p.file.name}](${p.file.name})` // タイトルまたはファイルリンク
    ])
);

完成

おわりに

また一つ便利になりました。