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,
|
WorkspaceLeaf,
|
||||||
Notice,
|
Notice,
|
||||||
TFile,
|
TFile,
|
||||||
|
TFolder,
|
||||||
TAbstractFile,
|
TAbstractFile,
|
||||||
getAllTags,
|
getAllTags,
|
||||||
moment,
|
moment,
|
||||||
@@ -527,31 +528,105 @@ export default class WaypointPlugin extends Plugin {
|
|||||||
if (!file) {
|
if (!file) {
|
||||||
file = await this.createPeriodNote(fullPath, periodSettings, date, config.label);
|
file = await this.createPeriodNote(fullPath, periodSettings, date, config.label);
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
new Notice(`Created ${config.label.toLowerCase()} note: ${filename}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = leaf || this.app.workspace.getLeaf(false);
|
const target = leaf || this.app.workspace.getLeaf(false);
|
||||||
await target.openFile(file);
|
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(
|
private async createPeriodNote(
|
||||||
fullPath: string,
|
fullPath: string,
|
||||||
periodSettings: PeriodNoteSettings,
|
periodSettings: PeriodNoteSettings,
|
||||||
date: moment.Moment,
|
date: moment.Moment,
|
||||||
label: string,
|
label: string,
|
||||||
): Promise<TFile | null> {
|
): Promise<TFile | null> {
|
||||||
|
const noun = label.toLowerCase();
|
||||||
|
const slash = fullPath.lastIndexOf('/');
|
||||||
|
const folder = slash < 0 ? '' : fullPath.slice(0, slash);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const templateFile = this.resolveTemplateFile(periodSettings.templateFile);
|
await this.ensureFolderExists(folder);
|
||||||
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);
|
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
new Notice(
|
||||||
new Notice(`Failed to create ${label.toLowerCase()} note: ${message}`);
|
`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;
|
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. */
|
/** 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);
|
const detected = this.detectPeriodType(file.basename);
|
||||||
if (!detected) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { period, date } = detected;
|
const { period, date } = detected;
|
||||||
|
|
||||||
if (!date.isValid()) {
|
|
||||||
new Notice(`Could not parse date from filename: ${file.basename}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const amount = direction === 'next' ? 1 : -1;
|
const amount = direction === 'next' ? 1 : -1;
|
||||||
|
|
||||||
// Map period to moment duration unit
|
// Map period to moment duration unit
|
||||||
@@ -649,3 +726,10 @@ function basenameFromPath(path: string): string {
|
|||||||
const name = path.slice(path.lastIndexOf('/') + 1);
|
const name = path.slice(path.lastIndexOf('/') + 1);
|
||||||
return name.replace(/\.[^/.]+$/, '');
|
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