Skip to content

Commit 1867d62

Browse files
jsonifyclaudeJason Rueckertgemini-code-assist[bot]
authored
Claude/add create template button 01 c9sv d4 nb2pfqo p7ps n1tn u (#119)
* Add 'Create New Template' button to Template Browser - Added prominent 'Create New Template' button in the header - Button triggers the existing noted.createCustomTemplate command - Auto-refreshes template list after creation - Provides quick access to template creation from browser UI * Add 'Create Template with Variables' button for JSON templates - Created new command noted.createTemplateWithVariables - Prompts for template name, description, and category - Creates JSON template with variable system support - Opens Template Browser and directs user to Variable Editor - Added dedicated button in Template Browser header - Provides clear separation between simple and advanced templates * Improve variable editor UX with usage guide and workflow Major improvements to help users understand how to use variables: 1. **Variable Usage Guide Panel**: - Replaced template preview with clear usage instructions - Shows step-by-step guide on how to use variables - Lists all custom variables with copy buttons - One-click copy of {variable_name} syntax 2. **Edit Template Content Button**: - Added prominent button in variable editor header - Opens template file directly from variable editor - Shows helpful tip when clicked 3. **Improved Save Flow**: - Success message now includes next steps - Keeps editor open for 8 seconds after save - Clear guidance to click "Edit Template Content" - User can immediately start using variables 4. **Enhanced Visual Feedback**: - Copy buttons show "✓ Copied!" confirmation - Green success banner with actionable next step - Variable types displayed as badges - Clearer separation between instructions and actions This addresses the issue where users could save variables but didn't know how to actually use them in their templates. * feat(template): enhance template creation with custom variable support * Update src/templates/templateBrowserView.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Update src/extension.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Fix CI/CD and address code review feedback **CI/CD Fixes**: - Add missing `import * as os from 'os'` for userInfo() usage - Remove reference to undefined `templatesProvider` in createTemplateWithVariables **Code Review: Race Condition Fixes**: 1. **Remove setTimeout race condition**: - Removed fragile 500ms setTimeout waiting for webview - Template browser auto-refreshes when opened - User gets clear instructions to click "Edit Variables" 2. **Fix TOCTOU race condition in file creation**: - Changed from access() check + writeFile() to atomic writeFile() with 'wx' flag - Prevents race condition where file could be created between check and write - Simplified error handling with specific EEXIST check **Code Review: Improvements**: 3. **Pre-fill author field with system username**: - Changed `author: ''` to `author: os.userInfo().username` - Provides better default value for template metadata 4. **Improve error handling in templateService.ts**: - Added error logging for JSON parsing failures - Distinguish between ENOENT (file not found) and parsing errors - Helps debugging malformed template files **Test Results**: ✓ All 579 unit tests passing ✓ TypeScript compilation successful ✓ No type errors or warnings --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Jason Rueckert <[email protected]> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 22a29f9 commit 1867d62

File tree

4 files changed

+436
-35
lines changed

4 files changed

+436
-35
lines changed

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@
257257
"title": "Noted: Create Custom Template",
258258
"icon": "$(add)"
259259
},
260+
{
261+
"command": "noted.createTemplateWithVariables",
262+
"title": "Noted: Create Template with Variables",
263+
"icon": "$(variable)"
264+
},
260265
{
261266
"command": "noted.editCustomTemplate",
262267
"title": "Noted: Edit Custom Template",

src/extension.ts

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
22
import * as path from 'path';
33
import * as fs from 'fs';
44
import { promises as fsp } from 'fs';
5+
import * as os from 'os';
56
import { showCalendarView } from './calendar/calendarView';
67
import { showGraphView } from './graph/graphView';
78
import { showActivityView } from './activity/activityView';
@@ -1754,6 +1755,11 @@ export function activate(context: vscode.ExtensionContext) {
17541755
templatesProvider.refresh();
17551756
});
17561757

1758+
// Command to create JSON template with variables
1759+
let createTemplateWithVariablesCmd = vscode.commands.registerCommand('noted.createTemplateWithVariables', async () => {
1760+
await createTemplateWithVariables();
1761+
});
1762+
17571763
// Command to edit custom template
17581764
let editCustomTemplateCmd = vscode.commands.registerCommand('noted.editCustomTemplate', async () => {
17591765
await editCustomTemplate();
@@ -2113,7 +2119,7 @@ export function activate(context: vscode.ExtensionContext) {
21132119
searchTagCmd, renameTagCmd, mergeTagsCmd, deleteTagCmd, exportTagsCmd,
21142120
showStats, exportNotes, duplicateNote, moveNotesFolder,
21152121
setupDefaultFolder, setupCustomFolder, showNotesConfig,
2116-
createCustomTemplate, editCustomTemplateCmd, deleteCustomTemplateCmd,
2122+
createCustomTemplate, createTemplateWithVariablesCmd, editCustomTemplateCmd, deleteCustomTemplateCmd,
21172123
duplicateCustomTemplateCmd, previewTemplateCmd, openTemplatesFolder,
21182124
createTemplateWithAI, enhanceTemplate, selectAIModel,
21192125
createBundle, createBundleFromTemplates, editBundle, deleteBundle,
@@ -2371,6 +2377,97 @@ Available placeholders:
23712377
}
23722378
}
23732379

2380+
async function createTemplateWithVariables() {
2381+
const templateName = await vscode.window.showInputBox({
2382+
prompt: 'Enter template name',
2383+
placeHolder: 'my-advanced-template',
2384+
validateInput: (value) => {
2385+
if (!value) {
2386+
return 'Template name is required';
2387+
}
2388+
if (!/^[a-z0-9-_]+$/i.test(value)) {
2389+
return 'Template name can only contain letters, numbers, hyphens, and underscores';
2390+
}
2391+
return null;
2392+
}
2393+
});
2394+
2395+
if (!templateName) {
2396+
return;
2397+
}
2398+
2399+
const description = await vscode.window.showInputBox({
2400+
prompt: 'Enter template description',
2401+
placeHolder: 'A brief description of what this template is for'
2402+
});
2403+
2404+
if (description === undefined) {
2405+
return;
2406+
}
2407+
2408+
const category = await vscode.window.showInputBox({
2409+
prompt: 'Enter template category',
2410+
placeHolder: 'General',
2411+
value: 'General'
2412+
});
2413+
2414+
const templatesPath = getTemplatesPath();
2415+
if (!templatesPath) {
2416+
vscode.window.showErrorMessage('Please configure notes folder first');
2417+
return;
2418+
}
2419+
2420+
try {
2421+
// Create templates folder if it doesn't exist
2422+
try {
2423+
await fsp.access(templatesPath);
2424+
} catch {
2425+
await fsp.mkdir(templatesPath, { recursive: true });
2426+
}
2427+
2428+
const templateFile = path.join(templatesPath, `${templateName}.json`);
2429+
2430+
// Create template object with system username as author
2431+
const template = {
2432+
id: templateName,
2433+
name: templateName.replace(/[-_]/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
2434+
description: description || '',
2435+
category: category || 'General',
2436+
tags: [],
2437+
version: '1.0.0',
2438+
author: os.userInfo().username,
2439+
difficulty: 'beginner',
2440+
variables: [],
2441+
content: `# {filename}\n\nCreated: {date} at {time}\nAuthor: {user}\n\n---\n\n[Your template content here]\n\nAdd custom variables using the Variable Editor to make this template interactive!`,
2442+
created: new Date().toISOString(),
2443+
modified: new Date().toISOString(),
2444+
usage_count: 0
2445+
};
2446+
2447+
// Use atomic write with 'wx' flag to prevent race conditions
2448+
try {
2449+
await fsp.writeFile(templateFile, JSON.stringify(template, null, 2), { flag: 'wx' });
2450+
2451+
vscode.window.showInformationMessage(`Template created: ${templateName}. Opening variable editor...`);
2452+
2453+
// Open the template browser
2454+
await vscode.commands.executeCommand('noted.showTemplateBrowser');
2455+
2456+
// Note: The template browser will refresh automatically when it opens
2457+
// The user will see the new template and can click "Edit Variables" to add custom variables
2458+
vscode.window.showInformationMessage(`Click "Edit Variables" on the "${templateName}" template to add custom variables.`);
2459+
} catch (error: any) {
2460+
if (error.code === 'EEXIST') {
2461+
vscode.window.showErrorMessage('Template already exists');
2462+
} else {
2463+
throw error;
2464+
}
2465+
}
2466+
} catch (error) {
2467+
vscode.window.showErrorMessage(`Failed to create template: ${error instanceof Error ? error.message : String(error)}`);
2468+
}
2469+
}
2470+
23742471
async function moveNotesFolderLocation() {
23752472
const workspaceFolders = vscode.workspace.workspaceFolders;
23762473
if (!workspaceFolders) {
@@ -2513,6 +2610,23 @@ async function createNoteFromTemplate(templateType: string) {
25132610
}
25142611

25152612
try {
2613+
// Check if template has custom variables
2614+
const { loadTemplateMetadata, generateTemplate: generateTemplateContent } = await import('./services/templateService');
2615+
const templateMetadata = await loadTemplateMetadata(templateType);
2616+
let variableValues: Record<string, any> | undefined;
2617+
2618+
// Collect variable values if template has variables
2619+
if (templateMetadata?.variables && templateMetadata.variables.length > 0) {
2620+
const { BundleService } = await import('./templates/BundleService');
2621+
const bundleService = new BundleService();
2622+
try {
2623+
variableValues = await bundleService.collectVariables(templateMetadata.variables);
2624+
} catch (error) {
2625+
// User cancelled or error collecting variables
2626+
return;
2627+
}
2628+
}
2629+
25162630
// Ask for note name
25172631
const noteName = await vscode.window.showInputBox({
25182632
prompt: 'Enter note name',
@@ -2552,7 +2666,7 @@ async function createNoteFromTemplate(templateType: string) {
25522666
return;
25532667
} catch {
25542668
// File doesn't exist, create it
2555-
const content = await generateTemplate(templateType, now, fileName);
2669+
const content = await generateTemplateContent(templateType, now, fileName, variableValues);
25562670
await fsp.writeFile(filePath, content);
25572671

25582672
const document = await vscode.workspace.openTextDocument(filePath);

src/services/templateService.ts

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@ function replacePlaceholders(content: string, date: Date, filename?: string): st
3636
return result;
3737
}
3838

39+
/**
40+
* Replace custom variables in template content
41+
*/
42+
function replaceCustomVariables(content: string, variableValues: Record<string, any>): string {
43+
let result = content;
44+
45+
for (const [name, value] of Object.entries(variableValues)) {
46+
// Replace all occurrences of {variable_name} with the value
47+
const pattern = new RegExp(`\\{${name}\\}`, 'g');
48+
result = result.replace(pattern, String(value ?? ''));
49+
}
50+
51+
return result;
52+
}
53+
3954
/**
4055
* Generate YAML frontmatter for a note
4156
*/
@@ -59,7 +74,7 @@ function generateFrontmatter(date: Date, filename?: string): string {
5974
/**
6075
* Generate template content for a note
6176
*/
62-
export async function generateTemplate(templateType: string | undefined, date: Date, filename?: string): Promise<string> {
77+
export async function generateTemplate(templateType: string | undefined, date: Date, filename?: string, variableValues?: Record<string, any>): Promise<string> {
6378
const dateStr = formatDateForNote(date);
6479
const yamlFrontmatter = generateFrontmatter(date, filename);
6580

@@ -80,13 +95,22 @@ export async function generateTemplate(templateType: string | undefined, date: D
8095
const jsonContent = await readFile(jsonPath);
8196
const template: Template = JSON.parse(jsonContent);
8297

83-
// Replace placeholders in template content
84-
const processedContent = replacePlaceholders(template.content, date, filename);
98+
// Replace built-in placeholders in template content
99+
let processedContent = replacePlaceholders(template.content, date, filename);
100+
101+
// Replace custom variables if provided
102+
if (variableValues) {
103+
processedContent = replaceCustomVariables(processedContent, variableValues);
104+
}
85105

86106
// JSON templates include their own frontmatter
87107
return processedContent;
88-
} catch {
89-
// JSON template doesn't exist, try legacy format
108+
} catch (error: any) {
109+
// Not a JSON template or doesn't exist. Log parsing errors for debugging.
110+
if (error.code !== 'ENOENT') {
111+
console.error(`Error parsing template metadata for '${templateType}.json':`, error);
112+
}
113+
// Try legacy format
90114
}
91115

92116
// Try legacy .txt/.md template
@@ -126,6 +150,26 @@ export async function generateTemplate(templateType: string | undefined, date: D
126150
return yamlFrontmatter;
127151
}
128152

153+
/**
154+
* Load template metadata including variables (for JSON templates)
155+
* Returns null for built-in or legacy templates
156+
*/
157+
export async function loadTemplateMetadata(templateType: string): Promise<Template | null> {
158+
const templatesPath = getTemplatesPath();
159+
if (!templatesPath) {
160+
return null;
161+
}
162+
163+
try {
164+
const jsonPath = path.join(templatesPath, `${templateType}.json`);
165+
const jsonContent = await readFile(jsonPath);
166+
return JSON.parse(jsonContent) as Template;
167+
} catch {
168+
// Not a JSON template or doesn't exist
169+
return null;
170+
}
171+
}
172+
129173
/**
130174
* Get list of custom template names
131175
*/

0 commit comments

Comments
 (0)