mirror of
https://github.com/formax68/memoChron.git
synced 2026-07-22 12:30:31 +00:00
Add customizable folder path templates for note organization
Implements flexible date-based subfolder system with 13 template variables:
- Date variables: {YYYY}, {YY}, {MM}, {M}, {MMM}, {MMMM}, {DD}, {D}, {DDD}, {DDDD}, {Q}
- Event variables: {source}, {event_title}
- Live preview in settings UI with validation
- Backwards compatible (empty template preserves current behavior)
- Comprehensive documentation and examples in README
Addresses GitHub issue #22 for organizing notes in date subfolders.
This commit is contained in:
parent
29c7aeeea7
commit
ce620b2bfb
5 changed files with 245 additions and 2 deletions
35
README.md
35
README.md
|
|
@ -57,6 +57,7 @@ It showcases a list of your calendar events. When you click on an event, it crea
|
|||
In the plugin settings, you can customize:
|
||||
|
||||
- Note location in your vault
|
||||
- **Folder path template** for organizing notes in date-based subfolders
|
||||
- Note title format
|
||||
- Default template with variables like:
|
||||
- {{event_title}}
|
||||
|
|
@ -68,6 +69,39 @@ In the plugin settings, you can customize:
|
|||
- {{location}}
|
||||
- {{source}}
|
||||
|
||||
### Organizing Notes with Folder Templates
|
||||
|
||||
MemoChron supports flexible folder organization using customizable templates. You can organize your event notes into date-based subfolders automatically.
|
||||
|
||||
**Available Template Variables:**
|
||||
- `{YYYY}` - 4-digit year (2025)
|
||||
- `{YY}` - 2-digit year (25)
|
||||
- `{MM}` - 2-digit month (06)
|
||||
- `{M}` - 1-digit month (6)
|
||||
- `{MMM}` - 3-letter month abbreviation (Jun)
|
||||
- `{MMMM}` - Full month name (June)
|
||||
- `{DD}` - 2-digit day (15)
|
||||
- `{D}` - 1-digit day (15)
|
||||
- `{DDD}` - 3-letter day abbreviation (Mon)
|
||||
- `{DDDD}` - Full day name (Monday)
|
||||
- `{Q}` - Quarter number (2)
|
||||
- `{source}` - Calendar source name
|
||||
- `{event_title}` - Event title (sanitized for file names)
|
||||
|
||||
**Example Templates:**
|
||||
- `{YYYY}/{MMM}` → `2025/Jun/`
|
||||
- `{YYYY}-{MM}` → `2025-06/`
|
||||
- `{source}/{YYYY}/{MMM}` → `Work Calendar/2025/Jun/`
|
||||
- `{YYYY}/Q{Q}` → `2025/Q2/`
|
||||
- `{MMM} {YYYY}` → `Jun 2025/`
|
||||
|
||||
**How to Use:**
|
||||
1. Go to Settings > MemoChron
|
||||
2. Find the "Folder path template" setting
|
||||
3. Enter your desired template pattern
|
||||
4. Use the live preview to see how your template will look
|
||||
5. Leave empty to save all notes in the same folder (default behavior)
|
||||
|
||||
## Configuration
|
||||
|
||||
### Settings
|
||||
|
|
@ -77,6 +111,7 @@ In the plugin settings, you can customize:
|
|||
- **Hide Calendar**: Show only the agenda view without the month calendar grid
|
||||
- **Refresh Interval**: Set how often calendar data updates
|
||||
- **Note Location**: Set the default folder for event notes
|
||||
- **Folder Path Template**: Organize notes in date-based subfolders with customizable patterns
|
||||
- **Note Title Format**: Customize how note titles are generated
|
||||
- **Template**: Customize the default note template
|
||||
- **Tags**: Set default tags for event notes
|
||||
|
|
|
|||
|
|
@ -14,6 +14,22 @@ interface EventTemplateVariables {
|
|||
description: string;
|
||||
}
|
||||
|
||||
interface FolderTemplateVariables {
|
||||
YYYY: string;
|
||||
YY: string;
|
||||
MM: string;
|
||||
M: string;
|
||||
MMM: string;
|
||||
MMMM: string;
|
||||
DD: string;
|
||||
D: string;
|
||||
DDD: string;
|
||||
DDDD: string;
|
||||
Q: string;
|
||||
source: string;
|
||||
event_title: string;
|
||||
}
|
||||
|
||||
export class NoteService {
|
||||
private static readonly NOTES_SECTION_MARKER = "## 📝 Notes";
|
||||
private static readonly FRONTMATTER_DELIMITER = "---";
|
||||
|
|
@ -63,10 +79,18 @@ export class NoteService {
|
|||
}
|
||||
|
||||
private buildFilePath(event: CalendarEvent): string {
|
||||
const { noteLocation, noteTitleFormat } = this.settings;
|
||||
const { noteLocation, noteTitleFormat, folderPathTemplate } = this.settings;
|
||||
const normalizedPath = normalizePath(noteLocation);
|
||||
const title = this.formatTitle(noteTitleFormat, event);
|
||||
return normalizePath(`${normalizedPath}/${title}.md`);
|
||||
|
||||
// If folderPathTemplate is empty, use the old behavior
|
||||
if (!folderPathTemplate.trim()) {
|
||||
return normalizePath(`${normalizedPath}/${title}.md`);
|
||||
}
|
||||
|
||||
// Apply folder template to create subfolder structure
|
||||
const subfolderPath = this.applyFolderTemplate(folderPathTemplate, event);
|
||||
return normalizePath(`${normalizedPath}/${subfolderPath}/${title}.md`);
|
||||
}
|
||||
|
||||
private async ensureParentFolder(filePath: string): Promise<void> {
|
||||
|
|
@ -298,6 +322,56 @@ export class NoteService {
|
|||
}
|
||||
}
|
||||
|
||||
private applyFolderTemplate(template: string, event: CalendarEvent): string {
|
||||
const variables = this.getFolderTemplateVariables(event);
|
||||
return this.parseFolderTemplate(template, variables);
|
||||
}
|
||||
|
||||
private getFolderTemplateVariables(event: CalendarEvent): FolderTemplateVariables {
|
||||
const date = event.start;
|
||||
const monthNames = [
|
||||
"January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
];
|
||||
const monthAbbreviations = [
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
||||
];
|
||||
const dayNames = [
|
||||
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
|
||||
];
|
||||
const dayAbbreviations = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth();
|
||||
const day = date.getDate();
|
||||
const dayOfWeek = date.getDay();
|
||||
const quarter = Math.floor(month / 3) + 1;
|
||||
|
||||
return {
|
||||
YYYY: year.toString(),
|
||||
YY: year.toString().slice(-2),
|
||||
MM: (month + 1).toString().padStart(2, "0"),
|
||||
M: (month + 1).toString(),
|
||||
MMM: monthAbbreviations[month],
|
||||
MMMM: monthNames[month],
|
||||
DD: day.toString().padStart(2, "0"),
|
||||
D: day.toString(),
|
||||
DDD: dayAbbreviations[dayOfWeek],
|
||||
DDDD: dayNames[dayOfWeek],
|
||||
Q: quarter.toString(),
|
||||
source: this.sanitizeFileName(event.source),
|
||||
event_title: this.sanitizeFileName(event.title),
|
||||
};
|
||||
}
|
||||
|
||||
private parseFolderTemplate(template: string, variables: FolderTemplateVariables): string {
|
||||
return Object.entries(variables).reduce((result, [key, value]) => {
|
||||
const pattern = new RegExp(`\\{${key}\\}`, "g");
|
||||
return result.replace(pattern, value);
|
||||
}, template);
|
||||
}
|
||||
|
||||
private sanitizeFileName(str: string): string {
|
||||
return str.replace(/[\\/:*?"<>|]/g, "-");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||
new Setting(this.containerEl).setName("Notes").setHeading();
|
||||
|
||||
this.renderNoteLocation();
|
||||
this.renderFolderPathTemplate();
|
||||
this.renderNoteTitleFormat();
|
||||
this.renderNoteDateFormat();
|
||||
this.renderDefaultFrontmatter();
|
||||
|
|
@ -318,6 +319,122 @@ export class SettingsTab extends PluginSettingTab {
|
|||
});
|
||||
}
|
||||
|
||||
private renderFolderPathTemplate(): void {
|
||||
const templateSetting = new Setting(this.containerEl)
|
||||
.setName("Folder path template")
|
||||
.setDesc("Organize notes in date-based subfolders. Leave empty to save all notes in the same folder.");
|
||||
|
||||
templateSetting.descEl.createEl("br");
|
||||
templateSetting.descEl.createEl("small", {
|
||||
text: "Available variables: {YYYY}, {YY}, {MM}, {M}, {MMM}, {MMMM}, {DD}, {D}, {DDD}, {DDDD}, {Q}, {source}, {event_title}"
|
||||
});
|
||||
templateSetting.descEl.createEl("br");
|
||||
templateSetting.descEl.createEl("small", {
|
||||
text: "Examples: {YYYY}/{MM}, {YYYY}-{MMM}, {source}/{YYYY}/{MMM}"
|
||||
});
|
||||
|
||||
templateSetting.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("{YYYY}/{MMM}")
|
||||
.setValue(this.plugin.settings.folderPathTemplate)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.folderPathTemplate = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// Add preview container
|
||||
const previewContainer = templateSetting.controlEl.createDiv({
|
||||
cls: "memochron-template-preview",
|
||||
});
|
||||
this.updateTemplatePreview(previewContainer, this.plugin.settings.folderPathTemplate);
|
||||
|
||||
// Update preview when input changes
|
||||
const textInput = templateSetting.controlEl.querySelector('input') as HTMLInputElement;
|
||||
if (textInput) {
|
||||
textInput.addEventListener('input', () => {
|
||||
this.updateTemplatePreview(previewContainer, textInput.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private updateTemplatePreview(container: HTMLElement, template: string): void {
|
||||
container.empty();
|
||||
|
||||
if (!template.trim()) {
|
||||
container.createEl("small", {
|
||||
text: "Preview: Notes will be saved directly in the note location folder",
|
||||
cls: "memochron-preview-text"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a sample date for preview
|
||||
const sampleDate = new Date();
|
||||
const sampleEvent = {
|
||||
title: "Sample Meeting",
|
||||
start: sampleDate,
|
||||
end: sampleDate,
|
||||
source: "Work Calendar"
|
||||
};
|
||||
|
||||
try {
|
||||
const previewPath = this.generatePreviewPath(template, sampleEvent);
|
||||
container.createEl("small", {
|
||||
text: `Preview: ${previewPath}/`,
|
||||
cls: "memochron-preview-text"
|
||||
});
|
||||
} catch (error) {
|
||||
container.createEl("small", {
|
||||
text: "Invalid template format",
|
||||
cls: "memochron-preview-error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private generatePreviewPath(template: string, event: any): string {
|
||||
const monthNames = [
|
||||
"January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
];
|
||||
const monthAbbreviations = [
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
||||
];
|
||||
const dayNames = [
|
||||
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
|
||||
];
|
||||
const dayAbbreviations = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
|
||||
const date = event.start;
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth();
|
||||
const day = date.getDate();
|
||||
const dayOfWeek = date.getDay();
|
||||
const quarter = Math.floor(month / 3) + 1;
|
||||
|
||||
const variables = {
|
||||
YYYY: year.toString(),
|
||||
YY: year.toString().slice(-2),
|
||||
MM: (month + 1).toString().padStart(2, "0"),
|
||||
M: (month + 1).toString(),
|
||||
MMM: monthAbbreviations[month],
|
||||
MMMM: monthNames[month],
|
||||
DD: day.toString().padStart(2, "0"),
|
||||
D: day.toString(),
|
||||
DDD: dayAbbreviations[dayOfWeek],
|
||||
DDDD: dayNames[dayOfWeek],
|
||||
Q: quarter.toString(),
|
||||
source: event.source.replace(/[\\/:*?"<>|]/g, "-"),
|
||||
event_title: event.title.replace(/[\\/:*?"<>|]/g, "-"),
|
||||
};
|
||||
|
||||
return Object.entries(variables).reduce((result, [key, value]) => {
|
||||
const pattern = new RegExp(`\\{${key}\\}`, "g");
|
||||
return result.replace(pattern, value);
|
||||
}, template);
|
||||
}
|
||||
|
||||
private renderDefaultTags(): void {
|
||||
new Setting(this.containerEl)
|
||||
.setName("Default tags")
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export interface MemoChronSettings {
|
|||
noteTemplate: string;
|
||||
firstDayOfWeek: number; // 0 = Sunday, 1 = Monday, etc.
|
||||
hideCalendar: boolean;
|
||||
folderPathTemplate: string; // Template for organizing notes in date-based subfolders
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: MemoChronSettings = {
|
||||
|
|
@ -54,4 +55,5 @@ export const DEFAULT_SETTINGS: MemoChronSettings = {
|
|||
`,
|
||||
firstDayOfWeek: DEFAULT_FIRST_DAY_OF_WEEK,
|
||||
hideCalendar: false,
|
||||
folderPathTemplate: "", // Empty by default for backwards compatibility
|
||||
};
|
||||
|
|
|
|||
15
styles.css
15
styles.css
|
|
@ -334,6 +334,21 @@
|
|||
background: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
/* Template Preview */
|
||||
.memochron-template-preview {
|
||||
margin-top: var(--memochron-padding-xs);
|
||||
}
|
||||
|
||||
.memochron-preview-text {
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.memochron-preview-error {
|
||||
color: var(--text-error);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Mobile Specific Styles */
|
||||
@media screen and (max-width: 768px) {
|
||||
.memochron-calendar-grid {
|
||||
|
|
|
|||
Loading…
Reference in a new issue