mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
feat: Unify template variables across filename and body templates
Both template systems now share the same set of variables: - Filename templates gain: contexts, tags, hashtags, timeEstimate, details, parentNote - Body templates gain: zettel, nano, and all extended date/time variables from filename templates Extended FilenameContext interface to support additional task properties while maintaining backwards compatibility. Updated documentation to reflect the unified variable system.
This commit is contained in:
parent
2b145a45da
commit
c3d58659ec
3 changed files with 180 additions and 0 deletions
|
|
@ -134,6 +134,13 @@ When using the **custom** filename format, you can create templates using variab
|
|||
- `{{status}}` - Task status (e.g., "todo", "in-progress", "done")
|
||||
- `{{dueDate}}` - Task due date (YYYY-MM-DD format)
|
||||
- `{{scheduledDate}}` - Task scheduled date (YYYY-MM-DD format)
|
||||
- `{{context}}` - First context from the task's contexts array
|
||||
- `{{contexts}}` - All contexts joined by `/`
|
||||
- `{{tags}}` - Task tags (comma-separated)
|
||||
- `{{hashtags}}` - Task tags as space-separated hashtags (e.g., "#work #urgent")
|
||||
- `{{timeEstimate}}` - Time estimate in minutes
|
||||
- `{{details}}` - Task details/description (truncated to 50 characters)
|
||||
- `{{parentNote}}` - Parent note name where task was created
|
||||
- `{{priorityShort}}` - First letter of priority in uppercase (e.g., "H")
|
||||
- `{{statusShort}}` - First letter of status in uppercase (e.g., "T")
|
||||
- `{{titleLower}}` - Task title in lowercase
|
||||
|
|
@ -266,6 +273,25 @@ For detailed reminder documentation, see [Task Reminders](../features/task-manag
|
|||
|
||||
TaskNotes supports **Templates** for both the YAML frontmatter and the body of your task notes. You can use templates to pre-fill common values, add boilerplate text, and create a consistent structure for your tasks. Templates can also include variables, such as `{{title}}`, `{{date}}`, and `{{parentNote}}`, which will be automatically replaced with the appropriate values when a new task is created.
|
||||
|
||||
### Unified Template Variables
|
||||
|
||||
Body templates now support the same variables as filename templates. All variables listed in the [Filename Template Variables](#filename-template-variables) section above are available in body templates, including:
|
||||
|
||||
- All date/time variables (`{{year}}`, `{{month}}`, `{{timestamp}}`, etc.)
|
||||
- All title variations (`{{titleKebab}}`, `{{titleSnake}}`, etc.)
|
||||
- Task property variations (`{{priorityShort}}`, `{{statusShort}}`)
|
||||
- Unique identifiers (`{{zettel}}`, `{{nano}}`)
|
||||
- Advanced variables (`{{unix}}`, `{{unixMs}}`, etc.)
|
||||
|
||||
### Body-Specific Variables
|
||||
|
||||
- `{{contexts}}` - Task contexts (comma-separated)
|
||||
- `{{tags}}` - Task tags (comma-separated)
|
||||
- `{{hashtags}}` - Task tags as space-separated hashtags
|
||||
- `{{timeEstimate}}` - Time estimate in minutes
|
||||
- `{{details}}` - User-provided details/description
|
||||
- `{{parentNote}}` - Parent note name/path (properly quoted for YAML)
|
||||
|
||||
The `{{parentNote}}` variable is particularly useful for project organization. It inserts the parent note as a properly formatted markdown link.
|
||||
|
||||
### Basic Usage
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ export interface FilenameContext {
|
|||
date?: Date;
|
||||
dueDate?: string; // YYYY-MM-DD format
|
||||
scheduledDate?: string; // YYYY-MM-DD format
|
||||
// Additional body template variables (optional for backwards compatibility)
|
||||
contexts?: string[];
|
||||
tags?: string[];
|
||||
timeEstimate?: number;
|
||||
details?: string;
|
||||
parentNote?: string;
|
||||
}
|
||||
|
||||
export interface ICSFilenameContext extends FilenameContext {
|
||||
|
|
@ -214,6 +220,10 @@ function generateCustomFilename(
|
|||
? context.status
|
||||
: "open";
|
||||
|
||||
// Process array values for contexts and tags
|
||||
const contexts = Array.isArray(context.contexts) ? context.contexts : [];
|
||||
const tags = Array.isArray(context.tags) ? context.tags : [];
|
||||
|
||||
const variables: Record<string, string> = {
|
||||
title: sanitizedTitle,
|
||||
date: format(date, "yyyy-MM-dd"),
|
||||
|
|
@ -230,6 +240,14 @@ function generateCustomFilename(
|
|||
second: format(date, "ss"),
|
||||
dueDate: context.dueDate || "",
|
||||
scheduledDate: context.scheduledDate || "",
|
||||
// Body template variables (contexts, tags, etc.)
|
||||
context: contexts[0] ? sanitizeForFilename(contexts[0]) : "",
|
||||
contexts: contexts.map((c) => sanitizeForFilename(c)).join("/"),
|
||||
tags: tags.map((t) => sanitizeForFilename(t)).join(", "),
|
||||
hashtags: tags.map((t) => `#${sanitizeForFilename(t)}`).join(" "),
|
||||
timeEstimate: context.timeEstimate?.toString() || "",
|
||||
details: context.details ? sanitizeForFilename(context.details.substring(0, 50)) : "",
|
||||
parentNote: context.parentNote ? sanitizeForFilename(context.parentNote) : "",
|
||||
// New date format variations
|
||||
shortDate: format(date, "yyMMdd"),
|
||||
shortYear: format(date, "yy"),
|
||||
|
|
|
|||
|
|
@ -196,6 +196,74 @@ function processTemplateVariablesForYaml(
|
|||
// {{time}} - Current time (basic format only)
|
||||
result = result.replace(/\{\{time\}\}/g, format(now, "HH:mm"));
|
||||
|
||||
// Extended date/time variables (consistent with filename templates)
|
||||
result = result.replace(/\{\{year\}\}/g, format(now, "yyyy"));
|
||||
result = result.replace(/\{\{month\}\}/g, format(now, "MM"));
|
||||
result = result.replace(/\{\{day\}\}/g, format(now, "dd"));
|
||||
result = result.replace(/\{\{hour\}\}/g, format(now, "HH"));
|
||||
result = result.replace(/\{\{minute\}\}/g, format(now, "mm"));
|
||||
result = result.replace(/\{\{second\}\}/g, format(now, "ss"));
|
||||
result = result.replace(/\{\{timestamp\}\}/g, format(now, "yyyy-MM-dd-HHmmss"));
|
||||
result = result.replace(/\{\{dateTime\}\}/g, format(now, "yyyy-MM-dd-HHmm"));
|
||||
result = result.replace(/\{\{shortDate\}\}/g, format(now, "yyMMdd"));
|
||||
result = result.replace(/\{\{shortYear\}\}/g, format(now, "yy"));
|
||||
result = result.replace(/\{\{monthName\}\}/g, format(now, "MMMM"));
|
||||
result = result.replace(/\{\{monthNameShort\}\}/g, format(now, "MMM"));
|
||||
result = result.replace(/\{\{dayName\}\}/g, format(now, "EEEE"));
|
||||
result = result.replace(/\{\{dayNameShort\}\}/g, format(now, "EEE"));
|
||||
result = result.replace(/\{\{week\}\}/g, format(now, "ww"));
|
||||
result = result.replace(/\{\{quarter\}\}/g, format(now, "q"));
|
||||
result = result.replace(/\{\{time12\}\}/g, format(now, "hh:mm a"));
|
||||
result = result.replace(/\{\{time24\}\}/g, format(now, "HH:mm"));
|
||||
result = result.replace(/\{\{hourPadded\}\}/g, format(now, "HH"));
|
||||
result = result.replace(/\{\{hour12\}\}/g, format(now, "hh"));
|
||||
result = result.replace(/\{\{ampm\}\}/g, format(now, "a"));
|
||||
result = result.replace(/\{\{unix\}\}/g, Math.floor(now.getTime() / 1000).toString());
|
||||
result = result.replace(/\{\{unixMs\}\}/g, now.getTime().toString());
|
||||
result = result.replace(/\{\{milliseconds\}\}/g, format(now, "SSS"));
|
||||
result = result.replace(/\{\{ms\}\}/g, format(now, "SSS"));
|
||||
result = result.replace(/\{\{timezone\}\}/g, format(now, "xxx"));
|
||||
result = result.replace(/\{\{timezoneShort\}\}/g, format(now, "xx"));
|
||||
result = result.replace(/\{\{utcOffset\}\}/g, format(now, "xxx"));
|
||||
result = result.replace(/\{\{utcOffsetShort\}\}/g, format(now, "xx"));
|
||||
result = result.replace(/\{\{utcZ\}\}/g, "Z");
|
||||
|
||||
// Date-based identifiers (consistent with filename templates)
|
||||
const datePart = format(now, "yyMMdd");
|
||||
const midnight = new Date(now);
|
||||
midnight.setHours(0, 0, 0, 0);
|
||||
const secondsSinceMidnight = Math.floor((now.getTime() - midnight.getTime()) / 1000);
|
||||
const zettelId = `${datePart}${secondsSinceMidnight.toString(36)}`;
|
||||
result = result.replace(/\{\{zettel\}\}/g, zettelId);
|
||||
result = result.replace(/\{\{nano\}\}/g, Date.now().toString() + Math.random().toString(36).substring(2, 7));
|
||||
|
||||
// Priority and status variations
|
||||
const priority = taskData.priority || "";
|
||||
const status = taskData.status || "";
|
||||
result = result.replace(/\{\{priorityShort\}\}/g, priority.substring(0, 1).toUpperCase());
|
||||
result = result.replace(/\{\{statusShort\}\}/g, status.substring(0, 1).toUpperCase());
|
||||
|
||||
// Title variations
|
||||
const titleForVariations = taskData.title || "";
|
||||
result = result.replace(/\{\{titleLower\}\}/g, titleForVariations.toLowerCase());
|
||||
result = result.replace(/\{\{titleUpper\}\}/g, titleForVariations.toUpperCase());
|
||||
result = result.replace(/\{\{titleSnake\}\}/g, titleForVariations.toLowerCase().replace(/\s+/g, "_"));
|
||||
result = result.replace(/\{\{titleKebab\}\}/g, titleForVariations.toLowerCase().replace(/\s+/g, "-"));
|
||||
result = result.replace(
|
||||
/\{\{titleCamel\}\}/g,
|
||||
titleForVariations
|
||||
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) =>
|
||||
index === 0 ? word.toLowerCase() : word.toUpperCase()
|
||||
)
|
||||
.replace(/\s+/g, "")
|
||||
);
|
||||
result = result.replace(
|
||||
/\{\{titlePascal\}\}/g,
|
||||
titleForVariations
|
||||
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word) => word.toUpperCase())
|
||||
.replace(/\s+/g, "")
|
||||
);
|
||||
|
||||
// ICS Event template variables (only available if taskData is ICSTemplateData)
|
||||
if ("icsEventTitle" in taskData) {
|
||||
const icsData = taskData as ICSTemplateData;
|
||||
|
|
@ -334,6 +402,74 @@ function processTemplateVariables(
|
|||
// {{time}} - Current time (basic format only)
|
||||
result = result.replace(/\{\{time\}\}/g, format(now, "HH:mm"));
|
||||
|
||||
// Extended date/time variables (consistent with filename templates)
|
||||
result = result.replace(/\{\{year\}\}/g, format(now, "yyyy"));
|
||||
result = result.replace(/\{\{month\}\}/g, format(now, "MM"));
|
||||
result = result.replace(/\{\{day\}\}/g, format(now, "dd"));
|
||||
result = result.replace(/\{\{hour\}\}/g, format(now, "HH"));
|
||||
result = result.replace(/\{\{minute\}\}/g, format(now, "mm"));
|
||||
result = result.replace(/\{\{second\}\}/g, format(now, "ss"));
|
||||
result = result.replace(/\{\{timestamp\}\}/g, format(now, "yyyy-MM-dd-HHmmss"));
|
||||
result = result.replace(/\{\{dateTime\}\}/g, format(now, "yyyy-MM-dd-HHmm"));
|
||||
result = result.replace(/\{\{shortDate\}\}/g, format(now, "yyMMdd"));
|
||||
result = result.replace(/\{\{shortYear\}\}/g, format(now, "yy"));
|
||||
result = result.replace(/\{\{monthName\}\}/g, format(now, "MMMM"));
|
||||
result = result.replace(/\{\{monthNameShort\}\}/g, format(now, "MMM"));
|
||||
result = result.replace(/\{\{dayName\}\}/g, format(now, "EEEE"));
|
||||
result = result.replace(/\{\{dayNameShort\}\}/g, format(now, "EEE"));
|
||||
result = result.replace(/\{\{week\}\}/g, format(now, "ww"));
|
||||
result = result.replace(/\{\{quarter\}\}/g, format(now, "q"));
|
||||
result = result.replace(/\{\{time12\}\}/g, format(now, "hh:mm a"));
|
||||
result = result.replace(/\{\{time24\}\}/g, format(now, "HH:mm"));
|
||||
result = result.replace(/\{\{hourPadded\}\}/g, format(now, "HH"));
|
||||
result = result.replace(/\{\{hour12\}\}/g, format(now, "hh"));
|
||||
result = result.replace(/\{\{ampm\}\}/g, format(now, "a"));
|
||||
result = result.replace(/\{\{unix\}\}/g, Math.floor(now.getTime() / 1000).toString());
|
||||
result = result.replace(/\{\{unixMs\}\}/g, now.getTime().toString());
|
||||
result = result.replace(/\{\{milliseconds\}\}/g, format(now, "SSS"));
|
||||
result = result.replace(/\{\{ms\}\}/g, format(now, "SSS"));
|
||||
result = result.replace(/\{\{timezone\}\}/g, format(now, "xxx"));
|
||||
result = result.replace(/\{\{timezoneShort\}\}/g, format(now, "xx"));
|
||||
result = result.replace(/\{\{utcOffset\}\}/g, format(now, "xxx"));
|
||||
result = result.replace(/\{\{utcOffsetShort\}\}/g, format(now, "xx"));
|
||||
result = result.replace(/\{\{utcZ\}\}/g, "Z");
|
||||
|
||||
// Date-based identifiers (consistent with filename templates)
|
||||
const datePartBody = format(now, "yyMMdd");
|
||||
const midnightBody = new Date(now);
|
||||
midnightBody.setHours(0, 0, 0, 0);
|
||||
const secondsSinceMidnightBody = Math.floor((now.getTime() - midnightBody.getTime()) / 1000);
|
||||
const zettelIdBody = `${datePartBody}${secondsSinceMidnightBody.toString(36)}`;
|
||||
result = result.replace(/\{\{zettel\}\}/g, zettelIdBody);
|
||||
result = result.replace(/\{\{nano\}\}/g, Date.now().toString() + Math.random().toString(36).substring(2, 7));
|
||||
|
||||
// Priority and status variations
|
||||
const priority = taskData.priority || "";
|
||||
const status = taskData.status || "";
|
||||
result = result.replace(/\{\{priorityShort\}\}/g, priority.substring(0, 1).toUpperCase());
|
||||
result = result.replace(/\{\{statusShort\}\}/g, status.substring(0, 1).toUpperCase());
|
||||
|
||||
// Title variations
|
||||
const titleForVariations = taskData.title || "";
|
||||
result = result.replace(/\{\{titleLower\}\}/g, titleForVariations.toLowerCase());
|
||||
result = result.replace(/\{\{titleUpper\}\}/g, titleForVariations.toUpperCase());
|
||||
result = result.replace(/\{\{titleSnake\}\}/g, titleForVariations.toLowerCase().replace(/\s+/g, "_"));
|
||||
result = result.replace(/\{\{titleKebab\}\}/g, titleForVariations.toLowerCase().replace(/\s+/g, "-"));
|
||||
result = result.replace(
|
||||
/\{\{titleCamel\}\}/g,
|
||||
titleForVariations
|
||||
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) =>
|
||||
index === 0 ? word.toLowerCase() : word.toUpperCase()
|
||||
)
|
||||
.replace(/\s+/g, "")
|
||||
);
|
||||
result = result.replace(
|
||||
/\{\{titlePascal\}\}/g,
|
||||
titleForVariations
|
||||
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word) => word.toUpperCase())
|
||||
.replace(/\s+/g, "")
|
||||
);
|
||||
|
||||
// ICS Event template variables (only available if taskData is ICSTemplateData)
|
||||
if ("icsEventTitle" in taskData) {
|
||||
const icsData = taskData as ICSTemplateData;
|
||||
|
|
|
|||
Loading…
Reference in a new issue