<%*
// Master Templater dispatcher — auto-detects note type via OSK plugin and
// applies the matching type-specific template.
//
// Both paths converge on write_template_to_file(tplFile, file), the same
// call the OSK plugin uses internally (main.js applyTemplaterTemplate). It
// targets a specific file by reference, so it sidesteps two failure modes
// of the prior implementation:
// - tp.file.include racing with the included template's tp.file.move
// (content can be written to the pre-move path and lost).
// - append_template_to_active_file reading a stale active file after the
// suggester modal closes (template applied to wrong file or no-op).
//
// Behavior:
// 1. File already has content → exit (idempotency guard, e.g. Periodic Notes).
// 2. File has a real name → recognize/pick on next tick → write template.
// 3. File is "Untitled" → register rename watcher, exit silently.
// On rename → recognize/pick → write template.
//
// Wired as the root folder_template in .obsidian/plugins/templater-obsidian/data.json.
if (tp.file.content && tp.file.content.length > 0) return;
const oskApi = app.plugins.plugins['obsidian-starter-kit']?.api; const tplPlugin = app.plugins.plugins['templater-obsidian']; if (oskApi == null || tplPlugin == null) return; if (typeof tplPlugin.templater?.write_template_to_file !== 'function') return;
const isUntitled = (name) => /^Untitled(\s+\d+)?$/i.test(name);
const recognizeOrPick = async (path) => {
const recognized = await oskApi.recognizeNoteType(path);
if (recognized && recognized.success === true && recognized.data != null) {
return recognized.data;
}
// Periodic-notes fallback — bypasses the suggester for wikilink navigation
// clicks (prev/next day, week breadcrumb, etc.). Detection is derived from
// the Periodic Notes plugin's own settings (single source of truth): each
// granularity's format and template are read live, so this never drifts
// when the user changes a format or template path in the plugin.
// A note's basename is matched against the leaf segment of the moment format
// via strict parsing — correct for week-year (gggg/ww) and quarter (Q) tokens
// that a plain \d{4} regex only approximates.
// Returns { templatePath } directly — the only field applyTemplate needs.
// The tp.file.move in each periodic template relocates the file from vault
// root to the correct subfolder automatically.
const basename = path.split('/').pop().replace(/.md$/i, '');
const pn = app.plugins.plugins['periodic-notes'];
let pnSettings = pn?.settings;
if (pnSettings == null) {
try { pnSettings = JSON.parse(await app.vault.adapter.read('.obsidian/plugins/periodic-notes/data.json')); }
catch (e) { pnSettings = null; }
}
if (pnSettings != null && window.moment != null) {
// Order matters: try finer granularities first so a shorter format cannot
// shadow a longer one (it can't with strict parsing, but keep it explicit).
for (const g of ['daily', 'weekly', 'monthly', 'quarterly', 'yearly']) {
const cfg = pnSettings[g];
if (!cfg || cfg.enabled === false || !cfg.format || !cfg.template) continue;
const fmtLeaf = cfg.format.split('/').pop(); // basename portion of the format
if (window.moment(basename, fmtLeaf, true).isValid()) {
return { templatePath: cfg.template };
}
}
}
const list = oskApi.listNoteTypes();
const types = (list && list.success === true && Array.isArray(list.data)) ? list.data : [];
const sorted = types.slice().sort((a, b) => (a.name || '').localeCompare(b.name || ''));
return await tp.system.suggester(t => t.name, sorted, false, 'Pick a note type (Esc to skip)');
};
const applyTemplate = async (file, noteType) => { if (!noteType || !noteType.templatePath) return; const tplFile = app.vault.getAbstractFileByPath(noteType.templatePath); if (tplFile == null) return; // Re-check idempotency: user may have typed between the dispatcher firing // and the template being applied. const content = await app.vault.read(file); if (content && content.trim().length > 0) return; await tplPlugin.templater.write_template_to_file(tplFile, file); };
const targetPath = tp.file.path(true); const targetFile = app.vault.getAbstractFileByPath(targetPath); if (targetFile == null) return;
// Path A: file has a real name. Defer to next tick so the dispatcher's render // (empty) finalizes first, then write the chosen template to the file. if (!isUntitled(targetFile.basename)) { setTimeout(async () => { const noteType = await recognizeOrPick(targetFile.path); await applyTemplate(targetFile, noteType); }, 0); return; }
// Path B: file is "Untitled". Register a one-time rename watcher. let dispatched = false; const ref = app.vault.on('rename', async (file, oldPath) => { if (dispatched) return; // Match by reference (Obsidian mutates path on the same TFile) or by oldPath. if (file !== targetFile && oldPath !== targetPath) return; if (isUntitled(file.basename)) return; dispatched = true; app.vault.offref(ref);
const noteType = await recognizeOrPick(file.path); await applyTemplate(file, noteType); });
// Safety: drop the watcher after 10 minutes if no rename happened. setTimeout(() => { if (!dispatched) { dispatched = true; app.vault.offref(ref); } }, 10 * 60 * 1000); -%>