mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
feat: add template variables support for default task folder (#413)
Enable dynamic folder creation using template variables in the tasksFolder setting.
Features:
- Task variables: {{context}}, {{project}}, {{priority}}, {{status}}, {{title}}
- Date variables: {{year}}, {{month}}, {{day}}, {{date}}
- Works for both regular task creation and inline conversion
- Automatic folder creation and title sanitization
- Comprehensive documentation with examples
Examples:
- Tasks/{{year}}/{{month}} → Tasks/2025/08
- {{project}}/{{priority}} → ProjectName/high
- {{context}}/{{date}} → WorkContext/2025-08-15
Resolves #376
This commit is contained in:
parent
cb3a0f1d71
commit
a1abe71a8c
2 changed files with 168 additions and 2 deletions
|
|
@ -8,6 +8,91 @@ You can specify a **Default Tasks Folder** where new tasks will be created. You
|
|||
|
||||
TaskNotes also provides a system for **Filename Generation**. You can choose from a variety of patterns, including title-based, timestamp-based, and Zettelkasten-style, or you can create your own custom filename template.
|
||||
|
||||
### Folder Template Variables
|
||||
|
||||
The **Default Tasks Folder** setting supports dynamic folder creation using template variables. This allows you to automatically organize tasks into folders based on their properties and the current date.
|
||||
|
||||
#### Available Template Variables
|
||||
|
||||
**Task Variables:**
|
||||
- `{{context}}` - First context from the task's contexts array
|
||||
- `{{project}}` - First project from the task's projects array
|
||||
- `{{priority}}` - Task priority (e.g., "high", "medium", "low")
|
||||
- `{{status}}` - Task status (e.g., "todo", "in-progress", "done")
|
||||
- `{{title}}` - Task title (sanitized for folder names)
|
||||
|
||||
**Date Variables:**
|
||||
- `{{year}}` - Current year (e.g., "2025")
|
||||
- `{{month}}` - Current month with leading zero (e.g., "08")
|
||||
- `{{day}}` - Current day with leading zero (e.g., "15")
|
||||
- `{{date}}` - Full current date (e.g., "2025-08-15")
|
||||
|
||||
#### Template Examples
|
||||
|
||||
**Date-based Organization:**
|
||||
```
|
||||
Tasks/{{year}}/{{month}}
|
||||
→ Tasks/2025/08
|
||||
|
||||
Tasks/{{year}}/{{date}}
|
||||
→ Tasks/2025/2025-08-15
|
||||
```
|
||||
|
||||
**Project-based Organization:**
|
||||
```
|
||||
{{project}}/{{year}}
|
||||
→ ProjectName/2025
|
||||
|
||||
Projects/{{project}}/{{context}}
|
||||
→ Projects/ProjectName/ContextName
|
||||
```
|
||||
|
||||
**Priority and Status Organization:**
|
||||
```
|
||||
Tasks/{{priority}}/{{status}}
|
||||
→ Tasks/high/todo
|
||||
|
||||
{{status}}/{{priority}}/{{year}}
|
||||
→ todo/high/2025
|
||||
```
|
||||
|
||||
**Mixed Organization:**
|
||||
```
|
||||
{{project}}/{{year}}/{{month}}/{{priority}}
|
||||
→ ProjectName/2025/08/high
|
||||
|
||||
Tasks/{{context}}/{{date}}
|
||||
→ Tasks/ContextName/2025-08-15
|
||||
```
|
||||
|
||||
#### Important Notes
|
||||
|
||||
- **Variable Processing**: Variables are processed when the task is created, using the actual task properties
|
||||
- **Missing Values**: If a task doesn't have a value for a variable (e.g., no context assigned), the variable is replaced with an empty string
|
||||
- **Multiple Values**: For arrays like contexts and projects, only the first value is used
|
||||
- **Title Sanitization**: The `{{title}}` variable automatically removes invalid folder characters (`<>:"/\|?*`) and replaces them with underscores
|
||||
- **Folder Creation**: Folders are automatically created if they don't exist
|
||||
- **Inline Tasks**: Template variables also work for the inline task conversion folder setting
|
||||
|
||||
#### Advanced Usage
|
||||
|
||||
**Conditional Folder Structures:**
|
||||
Since missing variables become empty strings, you can create conditional folder structures:
|
||||
```
|
||||
Tasks/{{project}}/{{context}}/{{year}}
|
||||
```
|
||||
- If both project and context exist: `Tasks/ProjectName/ContextName/2025`
|
||||
- If only project exists: `Tasks/ProjectName//2025` (note the double slash)
|
||||
- If neither exists: `Tasks///2025`
|
||||
|
||||
**Combining with Static Paths:**
|
||||
```
|
||||
Work/{{project}}/{{year}}/{{status}}
|
||||
Archive/{{year}}/{{month}}/{{project}}
|
||||
```
|
||||
|
||||
This feature provides powerful flexibility for automatically organizing your tasks into meaningful folder structures based on their properties and creation date.
|
||||
|
||||
### Store Title Exclusively in Filename
|
||||
|
||||
This setting provides an alternative way to manage your task titles. When enabled, the task's title will be used as the filename, and the `title` property will be removed from the frontmatter. This is a significant data storage change that simplifies frontmatter but disables all other filename templating options.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Notice, TFile, normalizePath, stringifyYaml } from 'obsidian';
|
|||
import { TemplateData, mergeTemplateFrontmatter, processTemplate } from '../utils/templateProcessor';
|
||||
import { addDTSTARTToRecurrenceRule, ensureFolderExists, updateToNextScheduledOccurrence } from '../utils/helpers';
|
||||
import { formatDateForStorage, getCurrentDateString, getCurrentTimestamp } from '../utils/dateUtils';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
import TaskNotesPlugin from '../main';
|
||||
|
||||
|
|
@ -20,6 +21,81 @@ export class TaskService {
|
|||
this.webhookNotifier = notifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a folder path template with task and date variables
|
||||
*
|
||||
* This method enables dynamic folder creation by replacing template variables
|
||||
* with actual values from the task data and current date.
|
||||
*
|
||||
* Supported task variables:
|
||||
* - {{context}} - First context from the task's contexts array
|
||||
* - {{project}} - First project from the task's projects array
|
||||
* - {{priority}} - Task priority (e.g., "high", "medium", "low")
|
||||
* - {{status}} - Task status (e.g., "todo", "in-progress", "done")
|
||||
* - {{title}} - Task title (sanitized for folder names)
|
||||
*
|
||||
* Supported date variables:
|
||||
* - {{year}} - Current year (e.g., "2025")
|
||||
* - {{month}} - Current month with leading zero (e.g., "08")
|
||||
* - {{day}} - Current day with leading zero (e.g., "15")
|
||||
* - {{date}} - Full current date (e.g., "2025-08-15")
|
||||
*
|
||||
* @param folderTemplate - The template string with variables to process
|
||||
* @param taskData - Optional task data for variable substitution
|
||||
* @param date - Date to use for date variables (defaults to current date)
|
||||
* @returns Processed folder path with variables replaced
|
||||
*
|
||||
* @example
|
||||
* processFolderTemplate("Tasks/{{year}}/{{month}}", taskData)
|
||||
* // Returns: "Tasks/2025/08"
|
||||
*
|
||||
* @example
|
||||
* processFolderTemplate("{{project}}/{{priority}}", taskData)
|
||||
* // Returns: "ProjectName/high"
|
||||
*/
|
||||
private processFolderTemplate(folderTemplate: string, taskData?: TaskCreationData, date: Date = new Date()): string {
|
||||
if (!folderTemplate) {
|
||||
return folderTemplate;
|
||||
}
|
||||
|
||||
let processedPath = folderTemplate;
|
||||
|
||||
// Replace task variables if taskData is provided
|
||||
if (taskData) {
|
||||
// Handle single context (first one if multiple)
|
||||
const context = Array.isArray(taskData.contexts) && taskData.contexts.length > 0
|
||||
? taskData.contexts[0]
|
||||
: '';
|
||||
processedPath = processedPath.replace(/\{\{context\}\}/g, context);
|
||||
|
||||
// Handle single project (first one if multiple)
|
||||
const project = Array.isArray(taskData.projects) && taskData.projects.length > 0
|
||||
? taskData.projects[0]
|
||||
: '';
|
||||
processedPath = processedPath.replace(/\{\{project\}\}/g, project);
|
||||
|
||||
// Handle priority
|
||||
const priority = taskData.priority || '';
|
||||
processedPath = processedPath.replace(/\{\{priority\}\}/g, priority);
|
||||
|
||||
// Handle status
|
||||
const status = taskData.status || '';
|
||||
processedPath = processedPath.replace(/\{\{status\}\}/g, status);
|
||||
|
||||
// Handle title (sanitized for folder names)
|
||||
const title = taskData.title ? taskData.title.replace(/[<>:"/\\|?*]/g, '_') : '';
|
||||
processedPath = processedPath.replace(/\{\{title\}\}/g, title);
|
||||
}
|
||||
|
||||
// Replace date variables with current date values
|
||||
processedPath = processedPath.replace(/\{\{year\}\}/g, format(date, 'yyyy'));
|
||||
processedPath = processedPath.replace(/\{\{month\}\}/g, format(date, 'MM'));
|
||||
processedPath = processedPath.replace(/\{\{day\}\}/g, format(date, 'dd'));
|
||||
processedPath = processedPath.replace(/\{\{date\}\}/g, format(date, 'yyyy-MM-dd'));
|
||||
|
||||
return processedPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new task file with all the necessary setup
|
||||
* This is the central method for task creation used by all components
|
||||
|
|
@ -68,6 +144,7 @@ export class TaskService {
|
|||
const baseFilename = generateTaskFilename(filenameContext, this.plugin.settings);
|
||||
|
||||
// Determine folder based on creation context
|
||||
// Process folder templates with task and date variables for dynamic folder organization
|
||||
let folder = '';
|
||||
if (taskData.creationContext === 'inline-conversion') {
|
||||
// For inline conversion, use the inline task folder setting with variable support
|
||||
|
|
@ -82,13 +159,17 @@ export class TaskService {
|
|||
} else {
|
||||
folder = inlineFolder;
|
||||
}
|
||||
// Process task and date variables in the inline folder path
|
||||
folder = this.processFolderTemplate(folder, taskData);
|
||||
} else {
|
||||
// Fallback to default tasks folder when inline folder is empty (#128)
|
||||
folder = this.plugin.settings.tasksFolder || '';
|
||||
const tasksFolder = this.plugin.settings.tasksFolder || '';
|
||||
folder = this.processFolderTemplate(tasksFolder, taskData);
|
||||
}
|
||||
} else {
|
||||
// For manual creation and other contexts, use the general tasks folder
|
||||
folder = this.plugin.settings.tasksFolder || '';
|
||||
const tasksFolder = this.plugin.settings.tasksFolder || '';
|
||||
folder = this.processFolderTemplate(tasksFolder, taskData);
|
||||
}
|
||||
|
||||
// Ensure folder exists
|
||||
|
|
|
|||
Loading…
Reference in a new issue