Obsidian dataviewjs ルーチン
環境
Obsidian Dataviewjs プラグイン
機能
notes/リピートタスク.md 内のリストから条件に一致する行を出力
コピーボタンクリックでタスク一覧をコピーできる
出力条件
### 🗂️ から始まる行は必ず出力
チェックリスト、かつ、@rpがついていない行は必ず出力
@rp 条件1,条件2 のように、条件はカンマ区切りで複数含めることができ、条件1 OR 条件2 を意味する
@rp 月,火 ファイル名の曜日と一致する場合のみ出力
@rp 毎月24日 ファイル名の日と一致する場合のみ出力
@rp 毎年1月19日 ファイル名の月日と一致する場合のみ出力
code:js
`dataviewjs
// === 設定 ==============================
const REPEAT_NOTE_PATH = "notes/リピートタスク.md";
// =====================================
// ▼ 日付(ファイル名)を取得:YYYY-MM-DD 前提
const fileName = dv.current()?.file?.name ?? "";
const date = window.moment(fileName, "YYYY-MM-DD", true);
if (!date.isValid()) {
dv.paragraph(⚠ このページのファイル名 "${fileName}" から日付(YYYY-MM-DD)を取得できませんでした。);
return;
}
// ▼ 日本語曜日
const youbiList = "日","月","火","水","木","金","土";
const youbi = youbiListdate.day();
// ▼ 条件判定(@rp OR)
function matchOneToken(token) {
const t = token.trim();
if (!t) return false;
if (youbiList.includes(t)) return t === youbi; // 曜日
let m = t.match(/^毎月\s*(\d{1,2})\s*日$/);
if (m) return date.date() === parseInt(m1,10);
m = t.match(/^毎年\s*(\d{1,2})\s*月\s*(\d{1,2})\s*日$/);
if (m) return (date.month()+1)===parseInt(m1,10) && date.date()===parseInt(m2,10);
return false;
}
const matchTokensOr = (tokens)=>tokens?.some(tok=>matchOneToken(tok)) ?? false;
// ▼ @rp 抽出・削除
const rpTokens = (s)=> (s.match(/@rp\s+(^\r\n+)/)?.1 ?? "")
.split(/,\uFF0C\u3001/).map(v=>v.trim()).filter(Boolean);
const removeRp = (s)=> s.replace(/\s*@rp.*$/, "").trimEnd();
// ▼ 原文行の取得(見出し用)
const afile = app.vault.getAbstractFileByPath(REPEAT_NOTE_PATH);
if (!afile) { dv.paragraph(⚠ 指定ノートが見つかりません: ${REPEAT_NOTE_PATH}); return; }
const raw = await app.vault.read(afile);
const lines = raw.split(/\r?\n/);
// ▼ DataviewのTask取得(見た目合わせのため必須)
const page = dv.page(REPEAT_NOTE_PATH);
const dvTasks = (page?.file?.tasks ?? dv.array([])).array();
const taskByLine = new Map(dvTasks.map(t => t.line, t)); // 1始まり
// ▼ 出力とコピー用のバッファ
const wrapper = dv.el("div","");
const copyBtn = dv.el("button","コピー",{style:"padding:6px 10px; border-radius:8px; cursor:pointer; border:1px solid var(--background-modifier-border);"},{parent:wrapper});
const block = dv.el("div","",{parent:wrapper});
let copyLines = [];
function flushTaskBuffer(buf) {
if (buf.length === 0) return;
// Dataviewの標準レンダラで描画
dv.taskList(dv.array(buf), false, block);
}
let buffer = []; // 連続タスクをまとめて描画するバッファ
// ▼ 行ごと処理:見出しはそのまま、タスクは条件適用
for (let i = 0; i < lines.length; i++) {
const lineNo = i + 1;
const line = linesi;
// 見出し:### 🗂️ は必ず出力
if (/^###\s*🗂️/.test(line)) {
flushTaskBuffer(buffer); buffer = [];
const h = removeRp(line);
dv.el("div", h.replace(/^###\s*/, ""), {
style: "font-size: var(--font-small); font-weight: 600; margin:0px 0 0px; color: var(--text-muted);"
}, block);
copyLines.push(h);
continue;
}
// タスク行かどうかを Dataview 側で判定(存在しなければ通常行として無視)
const t = taskByLine.get(lineNo);
if (!t) continue;
const hasRp = /@rp\b/.test(t.text);
const shouldOutput = !hasRp || matchTokensOr(rpTokens(t.text));
if (shouldOutput) {
// 表示用に @rp を除去した Task を複製
const t2 = Object.assign({}, t);
t2.text = removeRp(t.text);
buffer.push(t2);
copyLines.push(- [${t2.completed ? "x" : " "}] ${t2.text});
}
}
// 最後のタスク群を描画
flushTaskBuffer(buffer);
// ▼ コピー
const copyText = copyLines.join("\n");
copyBtn.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(copyText);
copyBtn.textContent = "コピーしました";
setTimeout(()=>copyBtn.textContent="コピー", 1200);
} catch {
copyBtn.textContent = "コピー失敗";
setTimeout(()=>copyBtn.textContent="コピー", 1200);
}
});
`