mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
feat(#1437): Unify template syntax to use {{variable}} everywhere
Filename templates now support both {{variable}} (preferred) and
{variable} (legacy) syntax for backwards compatibility. This makes
the syntax consistent with body/frontmatter templates.
Changes:
- filenameGenerator.ts: Process {{var}} first, then {var} as fallback
- titlePropertyCard.ts: Add deprecation warning for single-brace syntax
- en.ts: Add translation for legacy syntax warning
- docs/task-defaults.md: Update examples to use {{variable}} syntax
The single-brace syntax remains functional but users will see a warning
in settings encouraging migration to double-brace syntax.
This commit is contained in:
parent
0fd4594bfc
commit
2b145a45da
4 changed files with 106 additions and 60 deletions
|
|
@ -123,87 +123,89 @@ The system prevents file overwrites by checking for existing files and showing e
|
|||
|
||||
When using the **custom** filename format, you can create templates using variables that are replaced with actual values when tasks are created.
|
||||
|
||||
> **Recommended syntax:** Use double braces `{{variable}}` for consistency with body templates. Single braces `{variable}` are supported for backwards compatibility but are deprecated.
|
||||
|
||||
### Available Filename Variables
|
||||
|
||||
**Task Properties:**
|
||||
|
||||
- `{title}` - Task title (sanitized for filenames)
|
||||
- `{priority}` - Task priority (e.g., "high", "medium", "low")
|
||||
- `{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)
|
||||
- `{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
|
||||
- `{titleUpper}` - Task title in uppercase
|
||||
- `{titleSnake}` - Task title in snake_case
|
||||
- `{titleKebab}` - Task title in kebab-case
|
||||
- `{titleCamel}` - Task title in camelCase
|
||||
- `{titlePascal}` - Task title in PascalCase
|
||||
- `{{title}}` - Task title (sanitized for filenames)
|
||||
- `{{priority}}` - Task priority (e.g., "high", "medium", "low")
|
||||
- `{{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)
|
||||
- `{{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
|
||||
- `{{titleUpper}}` - Task title in uppercase
|
||||
- `{{titleSnake}}` - Task title in snake_case
|
||||
- `{{titleKebab}}` - Task title in kebab-case
|
||||
- `{{titleCamel}}` - Task title in camelCase
|
||||
- `{{titlePascal}}` - Task title in PascalCase
|
||||
|
||||
**Date and Time:**
|
||||
|
||||
- `{date}` - Full date (YYYY-MM-DD format, e.g., "2025-08-15")
|
||||
- `{time}` - Time as HHMMSS (e.g., "143502")
|
||||
- `{timestamp}` - Date and time as YYYY-MM-DD-HHMMSS (e.g., "2025-08-15-143502")
|
||||
- `{dateTime}` - Date and time as YYYY-MM-DD-HHMM (e.g., "2025-08-15-1435")
|
||||
- `{year}` - Year (e.g., "2025")
|
||||
- `{month}` - Month with leading zero (e.g., "08")
|
||||
- `{day}` - Day with leading zero (e.g., "15")
|
||||
- `{hour}` - Hour with leading zero (e.g., "14")
|
||||
- `{minute}` - Minute with leading zero (e.g., "35")
|
||||
- `{second}` - Second with leading zero (e.g., "02")
|
||||
- `{shortDate}` - Short date as YYMMDD (e.g., "250815")
|
||||
- `{shortYear}` - Short year as YY (e.g., "25")
|
||||
- `{monthName}` - Full month name (e.g., "August")
|
||||
- `{monthNameShort}` - Short month name (e.g., "Aug")
|
||||
- `{dayName}` - Full day name (e.g., "Thursday")
|
||||
- `{dayNameShort}` - Short day name (e.g., "Thu")
|
||||
- `{week}` - Week number (e.g., "33")
|
||||
- `{quarter}` - Quarter number (e.g., "3")
|
||||
- `{time12}` - 12-hour time with AM/PM (e.g., "02:35 PM")
|
||||
- `{time24}` - 24-hour time (e.g., "14:35")
|
||||
- `{hourPadded}` - Hour with leading zero (e.g., "14")
|
||||
- `{hour12}` - 12-hour format hour with leading zero (e.g., "02")
|
||||
- `{ampm}` - AM/PM indicator (e.g., "PM")
|
||||
- `{{date}}` - Full date (YYYY-MM-DD format, e.g., "2025-08-15")
|
||||
- `{{time}}` - Time as HHMMSS (e.g., "143502")
|
||||
- `{{timestamp}}` - Date and time as YYYY-MM-DD-HHMMSS (e.g., "2025-08-15-143502")
|
||||
- `{{dateTime}}` - Date and time as YYYY-MM-DD-HHMM (e.g., "2025-08-15-1435")
|
||||
- `{{year}}` - Year (e.g., "2025")
|
||||
- `{{month}}` - Month with leading zero (e.g., "08")
|
||||
- `{{day}}` - Day with leading zero (e.g., "15")
|
||||
- `{{hour}}` - Hour with leading zero (e.g., "14")
|
||||
- `{{minute}}` - Minute with leading zero (e.g., "35")
|
||||
- `{{second}}` - Second with leading zero (e.g., "02")
|
||||
- `{{shortDate}}` - Short date as YYMMDD (e.g., "250815")
|
||||
- `{{shortYear}}` - Short year as YY (e.g., "25")
|
||||
- `{{monthName}}` - Full month name (e.g., "August")
|
||||
- `{{monthNameShort}}` - Short month name (e.g., "Aug")
|
||||
- `{{dayName}}` - Full day name (e.g., "Thursday")
|
||||
- `{{dayNameShort}}` - Short day name (e.g., "Thu")
|
||||
- `{{week}}` - Week number (e.g., "33")
|
||||
- `{{quarter}}` - Quarter number (e.g., "3")
|
||||
- `{{time12}}` - 12-hour time with AM/PM (e.g., "02:35 PM")
|
||||
- `{{time24}}` - 24-hour time (e.g., "14:35")
|
||||
- `{{hourPadded}}` - Hour with leading zero (e.g., "14")
|
||||
- `{{hour12}}` - 12-hour format hour with leading zero (e.g., "02")
|
||||
- `{{ampm}}` - AM/PM indicator (e.g., "PM")
|
||||
|
||||
**Advanced:**
|
||||
|
||||
- `{unix}` - Unix timestamp in seconds (e.g., "1692106502")
|
||||
- `{unixMs}` - Unix timestamp in milliseconds (e.g., "1692106502123")
|
||||
- `{milliseconds}` - Milliseconds (e.g., "123")
|
||||
- `{ms}` - Milliseconds (e.g., "123")
|
||||
- `{timezone}` - Timezone offset (e.g., "+10:00")
|
||||
- `{timezoneShort}` - Short timezone offset (e.g., "+1000")
|
||||
- `{utcOffset}` - UTC offset (e.g., "+10:00")
|
||||
- `{utcOffsetShort}` - Short UTC offset (e.g., "+1000")
|
||||
- `{utcZ}` - UTC Z indicator (always "Z")
|
||||
- `{zettel}` - Zettelkasten ID (e.g., "250815abc")
|
||||
- `{nano}` - Nano ID with timestamp and random string
|
||||
- `{{unix}}` - Unix timestamp in seconds (e.g., "1692106502")
|
||||
- `{{unixMs}}` - Unix timestamp in milliseconds (e.g., "1692106502123")
|
||||
- `{{milliseconds}}` - Milliseconds (e.g., "123")
|
||||
- `{{ms}}` - Milliseconds (e.g., "123")
|
||||
- `{{timezone}}` - Timezone offset (e.g., "+10:00")
|
||||
- `{{timezoneShort}}` - Short timezone offset (e.g., "+1000")
|
||||
- `{{utcOffset}}` - UTC offset (e.g., "+10:00")
|
||||
- `{{utcOffsetShort}}` - Short UTC offset (e.g., "+1000")
|
||||
- `{{utcZ}}` - UTC Z indicator (always "Z")
|
||||
- `{{zettel}}` - Zettelkasten ID (e.g., "250815abc")
|
||||
- `{{nano}}` - Nano ID with timestamp and random string
|
||||
|
||||
### Filename Template Examples
|
||||
|
||||
**Date-based with title:**
|
||||
```
|
||||
{year}-{month}-{day} {title}
|
||||
{{year}}-{{month}}-{{day}} {{title}}
|
||||
```
|
||||
Result: `2025-08-15 Complete documentation.md`
|
||||
|
||||
**Zettelkasten with title:**
|
||||
```
|
||||
{zettel} {title}
|
||||
{{zettel}} {{title}}
|
||||
```
|
||||
Result: `250815abc Complete documentation.md`
|
||||
|
||||
**Priority and status prefix:**
|
||||
```
|
||||
[{priorityShort}] {title}
|
||||
[{{priorityShort}}] {{title}}
|
||||
```
|
||||
Result: `[H] Complete documentation.md`
|
||||
|
||||
**Custom timestamp:**
|
||||
```
|
||||
{timestamp}-{titleKebab}
|
||||
{{timestamp}}-{{titleKebab}}
|
||||
```
|
||||
Result: `2025-08-15-143502-complete-documentation.md`
|
||||
|
||||
|
|
@ -211,14 +213,14 @@ Result: `2025-08-15-143502-complete-documentation.md`
|
|||
|
||||
For ICS event notes, additional variables are available:
|
||||
|
||||
- `{icsEventTitle}` - Event title from ICS calendar
|
||||
- `{icsEventLocation}` - Event location
|
||||
- `{icsEventDescription}` - Event description (truncated to 50 characters)
|
||||
- `{icsEventTitleWithDate}` - Event title with formatted date
|
||||
- `{{icsEventTitle}}` - Event title from ICS calendar
|
||||
- `{{icsEventLocation}}` - Event location
|
||||
- `{{icsEventDescription}}` - Event description (truncated to 50 characters)
|
||||
- `{{icsEventTitleWithDate}}` - Event title with formatted date
|
||||
|
||||
### Important Notes
|
||||
|
||||
- **Single Braces**: Filename variables use single braces `{variable}` while folder variables use double braces `{{variable}}`
|
||||
- **Unified Syntax**: Both filename and body templates now use double braces `{{variable}}`. Single braces `{variable}` are supported for backwards compatibility but deprecated.
|
||||
- **Sanitization**: All variables are automatically sanitized to be safe for filenames (invalid characters removed)
|
||||
- **Empty Values**: If a property doesn't have a value, the variable is replaced with an empty string
|
||||
- **Character Limits**: Filenames are limited to 255 characters on most systems
|
||||
|
|
|
|||
|
|
@ -1011,6 +1011,7 @@ export const en: TranslationTree = {
|
|||
filenameUpdatesWithTitle: "Filename will automatically update when the task title changes.",
|
||||
filenameFormat: "Filename format:",
|
||||
customTemplate: "Custom template:",
|
||||
legacySyntaxWarning: "Single-brace syntax like {title} is deprecated. Please use double-brace syntax {{title}} instead for consistency with body templates.",
|
||||
},
|
||||
tagsCard: {
|
||||
nativeObsidianTags: "Uses native Obsidian tags",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { setIcon } from "obsidian";
|
||||
import TaskNotesPlugin from "../../../main";
|
||||
import {
|
||||
createCard,
|
||||
|
|
@ -153,10 +154,42 @@ function renderFilenameSettingsContent(
|
|||
plugin.settings.customFilenameTemplate
|
||||
);
|
||||
templateInput.style.width = "100%";
|
||||
|
||||
// Warning container for legacy syntax
|
||||
const warningContainer = container.createDiv();
|
||||
|
||||
const updateWarning = () => {
|
||||
warningContainer.empty();
|
||||
// Check for single-brace syntax that isn't part of double-brace
|
||||
// Match {word} but not {{word}}
|
||||
const template = templateInput.value;
|
||||
const hasLegacySyntax = /(?<!\{)\{([a-zA-Z]+)\}(?!\})/.test(template);
|
||||
|
||||
if (hasLegacySyntax) {
|
||||
const warningEl = warningContainer.createDiv({
|
||||
cls: "setting-item-description mod-warning",
|
||||
});
|
||||
warningEl.style.color = "var(--text-warning)";
|
||||
warningEl.style.marginTop = "8px";
|
||||
warningEl.style.display = "flex";
|
||||
warningEl.style.alignItems = "flex-start";
|
||||
warningEl.style.gap = "6px";
|
||||
|
||||
const iconEl = warningEl.createSpan();
|
||||
setIcon(iconEl, "alert-triangle");
|
||||
iconEl.style.flexShrink = "0";
|
||||
|
||||
const textEl = warningEl.createSpan();
|
||||
textEl.textContent = translate("settings.taskProperties.titleCard.legacySyntaxWarning");
|
||||
}
|
||||
};
|
||||
|
||||
templateInput.addEventListener("change", () => {
|
||||
plugin.settings.customFilenameTemplate = templateInput.value;
|
||||
save();
|
||||
updateWarning();
|
||||
});
|
||||
templateInput.addEventListener("input", updateWarning);
|
||||
templateContainer.appendChild(templateInput);
|
||||
|
||||
// Help text for template variables
|
||||
|
|
@ -164,5 +197,8 @@ function renderFilenameSettingsContent(
|
|||
text: translate("settings.appearance.taskFilenames.customTemplate.helpText"),
|
||||
cls: "setting-item-description",
|
||||
});
|
||||
|
||||
// Initial warning check
|
||||
updateWarning();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,17 +288,24 @@ function generateCustomFilename(
|
|||
}
|
||||
|
||||
// Replace all variables in the template
|
||||
// Support both {{variable}} (preferred) and {variable} (legacy) syntax
|
||||
Object.entries(variables).forEach(([key, value]) => {
|
||||
try {
|
||||
const regex = new RegExp(`\\{${key}\\}`, "g");
|
||||
result = result.replace(regex, value);
|
||||
// First: Replace double-brace syntax {{variable}} (preferred, consistent with body templates)
|
||||
const doubleRegex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
|
||||
result = result.replace(doubleRegex, value);
|
||||
|
||||
// Second: Replace single-brace syntax {variable} (legacy, backwards compatibility)
|
||||
const singleRegex = new RegExp(`\\{${key}\\}`, "g");
|
||||
result = result.replace(singleRegex, value);
|
||||
} catch (regexError) {
|
||||
console.warn(`Error replacing template variable ${key}:`, regexError);
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up any remaining unreplaced variables
|
||||
result = result.replace(/\{[^}]+\}/g, "");
|
||||
// Clean up any remaining unreplaced variables (both syntaxes)
|
||||
result = result.replace(/\{\{[^}]+\}\}/g, ""); // {{variable}}
|
||||
result = result.replace(/\{[^}]+\}/g, ""); // {variable}
|
||||
|
||||
// Ensure we have a valid filename
|
||||
if (!result.trim()) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue