fix: create missing period folders and explain failures
Clicking a week/month/quarter/year in a vault whose configured folder does not exist yet failed with a generic 'Failed to create X note: <raw error>', because vault.create refuses to create parent folders. Create the folder (and any missing ancestors) first, then report what actually went wrong. - Every failure path now names the offending path and the reason, and points at the relevant settings section; diagnostic notices stay up for 10s. - A configured-but-missing template used to fall back to basic frontmatter silently; the creation notice now says the template was not found. - The unreachable !date.isValid() branch after detectPeriodType is gone; the 'not a periodic note' notice now lists the configured formats it expected.
This commit is contained in:
+99
-15
@@ -5,6 +5,7 @@ import {
|
||||
WorkspaceLeaf,
|
||||
Notice,
|
||||
TFile,
|
||||
TFolder,
|
||||
TAbstractFile,
|
||||
getAllTags,
|
||||
moment,
|
||||
@@ -527,31 +528,105 @@ export default class WaypointPlugin extends Plugin {
|
||||
if (!file) {
|
||||
file = await this.createPeriodNote(fullPath, periodSettings, date, config.label);
|
||||
if (!file) return;
|
||||
new Notice(`Created ${config.label.toLowerCase()} note: ${filename}`);
|
||||
}
|
||||
|
||||
const target = leaf || this.app.workspace.getLeaf(false);
|
||||
await target.openFile(file);
|
||||
}
|
||||
|
||||
/** Create a period note from its template, or from minimal frontmatter. */
|
||||
/**
|
||||
* Create a period note from its template, or from minimal frontmatter.
|
||||
*
|
||||
* Every failure path names the offending path and the reason: the usual
|
||||
* cause is a configured folder that does not exist yet, which `vault.create`
|
||||
* refuses outright rather than creating.
|
||||
*/
|
||||
private async createPeriodNote(
|
||||
fullPath: string,
|
||||
periodSettings: PeriodNoteSettings,
|
||||
date: moment.Moment,
|
||||
label: string,
|
||||
): Promise<TFile | null> {
|
||||
const noun = label.toLowerCase();
|
||||
const slash = fullPath.lastIndexOf('/');
|
||||
const folder = slash < 0 ? '' : fullPath.slice(0, slash);
|
||||
|
||||
try {
|
||||
const templateFile = this.resolveTemplateFile(periodSettings.templateFile);
|
||||
const content = templateFile
|
||||
? await this.app.vault.read(templateFile)
|
||||
: `---\ntype: ${periodSettings.typeProperty}\ndate: ${date.format('YYYY-MM-DD')}\n---\n\n`;
|
||||
return await this.app.vault.create(fullPath, content);
|
||||
await this.ensureFolderExists(folder);
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
new Notice(`Failed to create ${label.toLowerCase()} note: ${message}`);
|
||||
new Notice(
|
||||
`Waypoint: could not create the folder "${folder}" for the ${noun} note.\n`
|
||||
+ `${describeError(err)}\n`
|
||||
+ 'Check Settings → Waypoint Sidebar → Periodic Notes.',
|
||||
DIAGNOSTIC_NOTICE_MS,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const configuredTemplate = periodSettings.templateFile;
|
||||
const templateFile = this.resolveTemplateFile(configuredTemplate);
|
||||
|
||||
let content: string;
|
||||
if (templateFile) {
|
||||
try {
|
||||
content = await this.app.vault.read(templateFile);
|
||||
} catch (err: unknown) {
|
||||
new Notice(
|
||||
`Waypoint: could not read the template "${templateFile.path}" for the ${noun} note.\n`
|
||||
+ describeError(err),
|
||||
DIAGNOSTIC_NOTICE_MS,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
content = `---\ntype: ${periodSettings.typeProperty}\ndate: ${date.format('YYYY-MM-DD')}\n---\n\n`;
|
||||
}
|
||||
|
||||
let file: TFile;
|
||||
try {
|
||||
file = await this.app.vault.create(fullPath, content);
|
||||
} catch (err: unknown) {
|
||||
new Notice(
|
||||
`Waypoint: could not create the ${noun} note at "${fullPath}".\n${describeError(err)}`,
|
||||
DIAGNOSTIC_NOTICE_MS,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
// A configured-but-missing template otherwise fails silently: the note
|
||||
// just appears with the fallback frontmatter and no explanation.
|
||||
if (configuredTemplate && !templateFile) {
|
||||
new Notice(
|
||||
`Created ${noun} note: ${file.basename}\n`
|
||||
+ `Template "${configuredTemplate}" was not found, so a basic note was created instead.`,
|
||||
DIAGNOSTIC_NOTICE_MS,
|
||||
);
|
||||
} else {
|
||||
new Notice(`Created ${noun} note: ${file.basename}`);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create `folder` and any missing ancestors.
|
||||
* `vault.create` throws when the parent folder is absent, so this runs first.
|
||||
*/
|
||||
private async ensureFolderExists(folder: string): Promise<void> {
|
||||
if (!folder) return;
|
||||
if (this.app.vault.getAbstractFileByPath(folder) instanceof TFolder) return;
|
||||
|
||||
let path = '';
|
||||
for (const segment of folder.split('/')) {
|
||||
if (!segment) continue;
|
||||
path = path ? `${path}/${segment}` : segment;
|
||||
if (this.app.vault.getAbstractFileByPath(path) instanceof TFolder) continue;
|
||||
try {
|
||||
await this.app.vault.createFolder(path);
|
||||
} catch (err: unknown) {
|
||||
// Someone else may have created it between the check and the call.
|
||||
if (!(this.app.vault.getAbstractFileByPath(path) instanceof TFolder)) throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The setting may or may not already carry the .md extension. */
|
||||
@@ -591,17 +666,19 @@ export default class WaypointPlugin extends Plugin {
|
||||
|
||||
const detected = this.detectPeriodType(file.basename);
|
||||
if (!detected) {
|
||||
new Notice('Current file is not a periodic note (daily/weekly/monthly/quarterly/yearly)');
|
||||
const formats = PERIOD_DETECTION_ORDER
|
||||
.map(p => `${PERIOD_CONFIGS[p].label.toLowerCase()} "${this.settings[PERIOD_CONFIGS[p].key].nameFormat}"`)
|
||||
.join(', ');
|
||||
new Notice(
|
||||
`Waypoint: "${file.basename}" does not match any configured periodic note format.\n`
|
||||
+ `Expected one of: ${formats}.`,
|
||||
DIAGNOSTIC_NOTICE_MS,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { period, date } = detected;
|
||||
|
||||
if (!date.isValid()) {
|
||||
new Notice(`Could not parse date from filename: ${file.basename}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const amount = direction === 'next' ? 1 : -1;
|
||||
|
||||
// Map period to moment duration unit
|
||||
@@ -649,3 +726,10 @@ function basenameFromPath(path: string): string {
|
||||
const name = path.slice(path.lastIndexOf('/') + 1);
|
||||
return name.replace(/\.[^/.]+$/, '');
|
||||
}
|
||||
|
||||
/** Notices that carry a diagnosis need longer on screen than the default. */
|
||||
const DIAGNOSTIC_NOTICE_MS = 10000;
|
||||
|
||||
function describeError(err: unknown): string {
|
||||
return err instanceof Error ? err.message : String(err);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user