diff --git a/README.md b/README.md index a42a0b9..31991e6 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,7 @@ Want to learn more? - ๐ŸŽจ **[THEMES.md](documentation/THEMES.md)** - Theme customization and creating custom themes - โœจ **[FEATURES.md](documentation/FEATURES.md)** - Complete feature list and keyboard shortcuts - ๐Ÿท๏ธ **[TAGS.md](documentation/TAGS.md)** - Organize notes with tags and combined filtering +- ๐Ÿ“‹ **[TEMPLATES.md](documentation/TEMPLATES.md)** - Create notes from reusable templates with dynamic placeholders - ๐Ÿงฎ **[MATHJAX.md](documentation/MATHJAX.md)** - LaTeX/Math notation examples and syntax reference - ๐Ÿ“Š **[MERMAID.md](documentation/MERMAID.md)** - Diagram creation with Mermaid (flowcharts, sequence diagrams, and more) - ๐Ÿ”Œ **[PLUGINS.md](documentation/PLUGINS.md)** - Plugin system and available plugins diff --git a/backend/main.py b/backend/main.py index 8b2539e..ad60af4 100644 --- a/backend/main.py +++ b/backend/main.py @@ -38,6 +38,9 @@ from .utils import ( validate_path_security, get_all_tags, get_notes_by_tag, + get_templates, + get_template_content, + apply_template_placeholders, ) from .plugins import PluginManager from .themes import get_available_themes, get_theme_css @@ -777,6 +780,101 @@ async def get_notes_by_tag_endpoint(tag_name: str): raise HTTPException(status_code=500, detail=safe_error_message(e, "Failed to get notes by tag")) +# --- Template Endpoints --- + +@api_router.get("/templates") +@limiter.limit("120/minute") +async def list_templates(request: Request): + """ + List all available templates from _templates folder. + + Returns: + List of template metadata + """ + try: + templates = get_templates(config['storage']['notes_dir']) + return {"templates": templates} + except Exception as e: + raise HTTPException(status_code=500, detail=safe_error_message(e, "Failed to list templates")) + + +@api_router.get("/templates/{template_name}") +@limiter.limit("120/minute") +async def get_template(request: Request, template_name: str): + """ + Get content of a specific template. + + Args: + template_name: Name of the template (without .md extension) + + Returns: + Template name and content + """ + try: + content = get_template_content(config['storage']['notes_dir'], template_name) + + if content is None: + raise HTTPException(status_code=404, detail="Template not found") + + return { + "name": template_name, + "content": content + } + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail=safe_error_message(e, "Failed to get template")) + + +@api_router.post("/templates/create-note") +@limiter.limit("60/minute") +async def create_note_from_template(request: Request, data: dict): + """ + Create a new note from a template with placeholder replacement. + + Args: + data: Dictionary containing templateName and notePath + + Returns: + Success status, path, and created content + """ + try: + template_name = data.get('templateName', '') + note_path = data.get('notePath', '') + + if not template_name or not note_path: + raise HTTPException(status_code=400, detail="Template name and note path required") + + # Get template content + template_content = get_template_content(config['storage']['notes_dir'], template_name) + + if template_content is None: + raise HTTPException(status_code=404, detail="Template not found") + + # Apply placeholder replacements + final_content = apply_template_placeholders(template_content, note_path) + + # Save the note + success = save_note(config['storage']['notes_dir'], note_path, final_content) + + if not success: + raise HTTPException(status_code=500, detail="Failed to create note from template") + + # Run plugin hooks + plugin_manager.run_hook('on_note_create', note_path=note_path, initial_content=final_content) + + return { + "success": True, + "path": note_path, + "message": "Note created from template successfully", + "content": final_content + } + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail=safe_error_message(e, "Failed to create note from template")) + + # --- Notes Endpoints --- @api_router.get("/notes") diff --git a/backend/utils.py b/backend/utils.py index 8459805..4d97d8b 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -647,3 +647,118 @@ def get_notes_by_tag(notes_dir: str, tag: str) -> List[Dict]: return matching_notes + +# ============================================================================ +# Template Functions +# ============================================================================ + +def get_templates(notes_dir: str) -> List[Dict]: + """ + Get all templates from the _templates folder. + + Args: + notes_dir: Base notes directory + + Returns: + List of template metadata (name, path, modified) + """ + templates = [] + templates_path = Path(notes_dir) / "_templates" + + if not templates_path.exists(): + return templates + + # Security check: ensure _templates folder is within notes directory + if not validate_path_security(notes_dir, templates_path): + print(f"Security: Templates directory is outside notes directory: {templates_path}") + return templates + + try: + for template_file in templates_path.glob("*.md"): + try: + # Security check: ensure each template is within notes directory + if not validate_path_security(notes_dir, template_file): + print(f"Security: Skipping template outside notes directory: {template_file}") + continue + + stat = template_file.stat() + templates.append({ + "name": template_file.stem, + "path": str(template_file.relative_to(notes_dir).as_posix()), + "modified": datetime.fromtimestamp(stat.st_mtime).isoformat() + }) + except Exception as e: + print(f"Error reading template {template_file}: {e}") + continue + except Exception as e: + print(f"Error accessing templates directory: {e}") + + return sorted(templates, key=lambda x: x['name']) + + +def get_template_content(notes_dir: str, template_name: str) -> Optional[str]: + """ + Get the content of a specific template. + + Args: + notes_dir: Base notes directory + template_name: Name of the template (without .md extension) + + Returns: + Template content or None if not found + """ + template_path = Path(notes_dir) / "_templates" / f"{template_name}.md" + + if not template_path.exists(): + return None + + # Security check: ensure template is within notes directory + if not validate_path_security(notes_dir, template_path): + print(f"Security: Template path is outside notes directory: {template_path}") + return None + + try: + with open(template_path, 'r', encoding='utf-8') as f: + return f.read() + except Exception as e: + print(f"Error reading template {template_name}: {e}") + return None + + +def apply_template_placeholders(content: str, note_path: str) -> str: + """ + Replace template placeholders with actual values. + + Supported placeholders: + {{date}} - Current date (YYYY-MM-DD) + {{time}} - Current time (HH:MM:SS) + {{datetime}} - Current datetime (YYYY-MM-DD HH:MM:SS) + {{timestamp}} - Unix timestamp + {{title}} - Note name without extension + {{folder}} - Parent folder name + + Args: + content: Template content with placeholders + note_path: Path of the note being created + + Returns: + Content with placeholders replaced + """ + now = datetime.now() + note = Path(note_path) + + replacements = { + '{{date}}': now.strftime('%Y-%m-%d'), + '{{time}}': now.strftime('%H:%M:%S'), + '{{datetime}}': now.strftime('%Y-%m-%d %H:%M:%S'), + '{{timestamp}}': str(int(now.timestamp())), + '{{title}}': note.stem, + '{{folder}}': note.parent.name if str(note.parent) != '.' else 'Root', + } + + result = content + for placeholder, value in replacements.items(): + result = result.replace(placeholder, value) + + return result + diff --git a/docs/index.html b/docs/index.html index e9f3244..f8dbcc8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -7,8 +7,8 @@ NoteDiscovery - Your Self-Hosted Knowledge Base - - + + @@ -17,7 +17,7 @@ - + @@ -28,7 +28,7 @@ - + @@ -509,7 +509,7 @@

Take Control of Your Notes

-

A lightweight, privacy-focused note-taking application with powerful features: LaTeX math equations, Mermaid diagrams, code highlighting, and more. Write, organize, and discover your notes with a beautiful, modern interfaceโ€”all running on your own server.

+

A lightweight, privacy-focused note-taking application with powerful features: LaTeX math equations, Mermaid diagrams, smart tags, custom templates, code highlighting, and more. Write, organize, and discover your notes with a beautiful, modern interfaceโ€”all running on your own server.

Get Started on GitHub โ†’
@@ -568,6 +568,18 @@

Syntax highlighting for popular coding languages. Developer-friendly notes.

+
+
๐Ÿท๏ธ
+

Smart Tags

+

Organize with YAML frontmatter tags. Filter and discover notes instantly.

+
+ +
+
๐Ÿ“‹
+

Note Templates

+

Create from custom templates with dynamic placeholders. Meeting notes, journals, and more.

+
+
๐Ÿ“‚

Simple Storage

@@ -642,7 +654,15 @@ ๐Ÿ”—
Internal Links - Connect notes with markdown links + Connect notes with backlinks +
+
+ +
+ ๐Ÿ–ผ๏ธ +
+ Image Support + Drag & drop images directly
diff --git a/documentation/API.md b/documentation/API.md index 96385bd..fd3c567 100644 --- a/documentation/API.md +++ b/documentation/API.md @@ -295,6 +295,120 @@ Self-documenting endpoint listing all available API routes. --- +## ๐Ÿท๏ธ Tags + +### List All Tags +`GET /api/tags` + +Returns all tags found in notes with their usage counts. + +**Response:** +```json +{ + "tags": { + "python": 5, + "tutorial": 3, + "backend": 2 + } +} +``` + +### Get Notes by Tag +`GET /api/tags/{tag_name}` + +Returns all notes that have a specific tag. + +**Response:** +```json +{ + "tag": "python", + "notes": [ + { + "path": "tutorials/python-basics.md", + "name": "python-basics", + "folder": "tutorials", + "tags": ["python", "tutorial"] + } + ] +} +``` + +--- + +## ๐Ÿ“„ Templates + +### List Templates +`GET /api/templates` + +Returns all available note templates from the `_templates` folder. + +**Response:** +```json +{ + "templates": [ + { + "name": "meeting-notes", + "path": "_templates/meeting-notes.md", + "modified": "2025-11-26T10:30:00" + }, + { + "name": "daily-journal", + "path": "_templates/daily-journal.md", + "modified": "2025-11-26T10:25:00" + } + ] +} +``` + +### Get Template Content +`GET /api/templates/{template_name}` + +Returns the content of a specific template. + +**Parameters:** +- `template_name` - Template name (without .md extension) + +**Response:** +```json +{ + "name": "meeting-notes", + "content": "# Meeting Notes\n\nDate: {{date}}\n..." +} +``` + +### Create Note from Template +`POST /api/templates/create-note` + +Creates a new note from a template with placeholder replacement. + +**Request Body:** +```json +{ + "templateName": "meeting-notes", + "notePath": "meetings/weekly-sync.md" +} +``` + +**Placeholders:** +- `{{date}}` - Current date (YYYY-MM-DD) +- `{{time}}` - Current time (HH:MM:SS) +- `{{datetime}}` - Current datetime +- `{{timestamp}}` - Unix timestamp +- `{{title}}` - Note name without extension +- `{{folder}}` - Parent folder name + +**Response:** +```json +{ + "success": true, + "path": "meetings/weekly-sync.md", + "message": "Note created from template successfully", + "content": "# Meeting Notes\n\nDate: 2025-11-26\n..." +} +``` + +--- + ## ๐Ÿ“ Response Format All endpoints return JSON responses: diff --git a/documentation/FEATURES.md b/documentation/FEATURES.md index 7509ef7..9b6be0a 100644 --- a/documentation/FEATURES.md +++ b/documentation/FEATURES.md @@ -7,6 +7,7 @@ - **Three view modes**: Edit, Split, Preview - **Auto-save** - Never lose your work - **Undo/Redo** - Ctrl+Z / Ctrl+Y support +- **Note templates** - Create notes from templates with dynamic placeholders - **Syntax highlighting** for code blocks (50+ languages) - **Copy code blocks** - One-click copy button on hover - **LaTeX/Math rendering** - Beautiful mathematical equations with MathJax (see [MATHJAX.md](MATHJAX.md)) @@ -162,6 +163,51 @@ graph TD ๐Ÿ“„ **See the [MERMAID](MERMAID.md) note for diagram examples and syntax reference.** +## ๐Ÿ“„ Note Templates + +Create notes from reusable templates with dynamic placeholder replacement. + +### Creating Templates +1. Create markdown files in `data/_templates/` folder +2. Use placeholders for dynamic content +3. Templates appear in the "New from Template" menu + +### Available Placeholders +- `{{date}}` - Current date (YYYY-MM-DD) +- `{{time}}` - Current time (HH:MM:SS) +- `{{datetime}}` - Current date and time +- `{{timestamp}}` - Unix timestamp +- `{{title}}` - Note name (without extension) +- `{{folder}}` - Parent folder name + +### Example Template +```markdown +--- +tags: [meeting] +date: {{date}} +--- + +# {{title}} + +**Created:** {{datetime}} + +## Notes + +``` + +### Using Templates +1. Click the "New" dropdown button +2. Select "New from Template" +3. Choose a template and enter a note name +4. The new note will be created with placeholders replaced + +### Built-in Templates +- **meeting-notes** - Template for meeting notes +- **daily-journal** - Daily journal with morning goals and evening reflection +- **project-plan** - Project planning template with objectives and timeline + +๐Ÿ“š **See [TEMPLATES.md](TEMPLATES.md)** for detailed documentation and example templates you can copy to your instance. + ## โšก Keyboard Shortcuts ### General diff --git a/documentation/TEMPLATES.md b/documentation/TEMPLATES.md new file mode 100644 index 0000000..fb88c5a --- /dev/null +++ b/documentation/TEMPLATES.md @@ -0,0 +1,135 @@ +# ๐Ÿ“‹ Note Templates + +Create notes from reusable templates with dynamic placeholder replacement. + +## Overview + +Templates allow you to quickly create notes with predefined structures and content. Perfect for recurring note types like meeting notes, daily journals, project plans, and more. + +## How to Use Templates + +### 1. Create Template Files + +Templates are stored in the `data/_templates/` folder as markdown files. You will need to create such folder if it doesn't exist: + +``` +data/ +โ””โ”€โ”€ _templates/ + โ”œโ”€โ”€ meeting-notes.md + โ”œโ”€โ”€ daily-journal.md + โ””โ”€โ”€ project-plan.md +``` + +### 2. Access Templates + +Click the **"New"** button (or **+** on any folder) and select **"New from Template"**: + +1. Choose a template from the dropdown +2. Enter a name for your new note +3. Click "Create Note" + +The template will be copied with all placeholders replaced automatically! + +### 3. Use Placeholders + +Templates support dynamic placeholders that are replaced when you create a note: + +| Placeholder | Description | Example | +|------------|-------------|---------| +| `{{date}}` | Current date | `2025-11-26` | +| `{{time}}` | Current time | `14:30:45` | +| `{{datetime}}` | Current date and time | `2025-11-26 14:30:45` | +| `{{timestamp}}` | Unix timestamp | `1732632645` | +| `{{title}}` | Note name (without .md) | `Weekly Meeting` | +| `{{folder}}` | Parent folder name | `Projects` | + +### Example Template + +```markdown +--- +tags: [meeting] +date: {{date}} +--- + +# Meeting Notes - {{title}} + +**Date:** {{datetime}} +**Participants:** +- + +## Agenda +- + +## Discussion + + +## Action Items +- [ ] + +## Next Steps + + +``` + +When you create a note called "Team Sync" from this template, it becomes: + +```markdown +--- +tags: [meeting] +date: 2025-11-26 +--- + +# Meeting Notes - Team Sync + +**Date:** 2025-11-26 14:30:45 +**Participants:** +- + +## Agenda +- + +## Discussion + + +## Action Items +- [ ] + +## Next Steps + + +``` + +## Example Templates + +We provide three example templates in `documentation/templates/` that you can copy to your `data/_templates/` folder: + +1. **meeting-notes.md** - Structured meeting notes with agenda, discussion, and action items +2. **daily-journal.md** - Daily journal with morning goals and evening reflection +3. **project-plan.md** - Project planning template with objectives, timeline, and status tracking + +### Using Example Templates + +**Option 1: Copy Manually** +```bash +cp documentation/templates/*.md data/_templates/ +``` + +**Option 2: Create Your Own** +1. Create a `.md` file in `data/_templates/` +2. Add content and placeholders +3. Save and it's ready to use! + +## Tips + +- โœ… Templates can include YAML frontmatter for tags and metadata +- โœ… Use descriptive template names (they appear in the dropdown) +- โœ… Templates work in any folder - the context is preserved +- โœ… You can edit templates anytime - changes apply to new notes only +- โœ… Combine templates with tags for powerful organization + +--- + +**See also:** +- [Tags Documentation](TAGS.md) - Learn about organizing with tags +- [Features Overview](FEATURES.md) - All application features + diff --git a/documentation/templates/daily-journal.md b/documentation/templates/daily-journal.md new file mode 100644 index 0000000..f3843e9 --- /dev/null +++ b/documentation/templates/daily-journal.md @@ -0,0 +1,25 @@ +--- +tags: [journal, daily] +date: {{date}} +--- + +# Daily Journal - {{date}} + +## ๐ŸŒ… Morning +**Time:** {{time}} + +### Goals for Today +- [ ] +- [ ] +- [ ] + +## ๐Ÿ“ Notes + + +## ๐ŸŒ™ Evening Reflection + + +## ๐Ÿ’ก Ideas + + + diff --git a/documentation/templates/meeting-notes.md b/documentation/templates/meeting-notes.md new file mode 100644 index 0000000..220ec44 --- /dev/null +++ b/documentation/templates/meeting-notes.md @@ -0,0 +1,24 @@ +--- +tags: [meeting] +date: {{date}} +--- + +# Meeting Notes - {{title}} + +**Date:** {{datetime}} +**Participants:** +- + +## Agenda +- + +## Discussion + + +## Action Items +- [ ] + +## Next Steps + + + diff --git a/documentation/templates/project-plan.md b/documentation/templates/project-plan.md new file mode 100644 index 0000000..25cee3d --- /dev/null +++ b/documentation/templates/project-plan.md @@ -0,0 +1,34 @@ +--- +tags: [project] +created: {{datetime}} +folder: {{folder}} +--- + +# Project: {{title}} + +## ๐Ÿ“‹ Overview + + +## ๐ŸŽฏ Objectives +- + +## ๐Ÿ“… Timeline +- **Start Date:** {{date}} +- **Target Completion:** + +## ๐Ÿ“ฆ Deliverables +- + +## ๐Ÿ‘ฅ Team + + +## ๐Ÿ“Š Status +- [ ] Planning +- [ ] In Progress +- [ ] Review +- [ ] Completed + +## ๐Ÿ“ Notes + + + diff --git a/frontend/app.js b/frontend/app.js index 6e3bc15..811f718 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -99,6 +99,14 @@ function noteApp() { // Dropdown state showNewDropdown: false, + dropdownTargetFolder: '', // Folder context for "New" dropdown (empty = root) + dropdownPosition: { top: 0, left: 0 }, // Position for contextual dropdown + + // Template state + showTemplateModal: false, + availableTemplates: [], + selectedTemplate: '', + newTemplateNoteName: '', // Homepage state selectedHomepageFolder: '', @@ -258,6 +266,7 @@ function noteApp() { await this.loadThemes(); await this.initTheme(); await this.loadNotes(); + await this.loadTemplates(); await this.checkStatsPlugin(); this.loadSidebarWidth(); this.loadEditorWidth(); @@ -591,6 +600,72 @@ function noteApp() { this.applyFilters(); }, + // ======================================================================== + // Template Methods + // ======================================================================== + + // Load available templates from _templates folder + async loadTemplates() { + try { + const response = await fetch('/api/templates'); + const data = await response.json(); + this.availableTemplates = data.templates || []; + } catch (error) { + ErrorHandler.handle('load templates', error, false); // Don't show alert, templates are optional + } + }, + + // Create a new note from a template + async createNoteFromTemplate() { + if (!this.selectedTemplate || !this.newTemplateNoteName.trim()) { + return; + } + + try { + // Determine the note path based on dropdown context + let notePath = this.newTemplateNoteName.trim(); + if (!notePath.endsWith('.md')) { + notePath += '.md'; + } + + // If we have a target folder context, create in that folder + if (this.dropdownTargetFolder || this.selectedHomepageFolder) { + const targetFolder = this.dropdownTargetFolder || this.selectedHomepageFolder; + notePath = `${targetFolder}/${notePath}`; + } + + // Create note from template + const response = await fetch('/api/templates/create-note', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + templateName: this.selectedTemplate, + notePath: notePath + }) + }); + + if (!response.ok) { + const error = await response.json(); + alert(error.detail || 'Failed to create note from template'); + return; + } + + const data = await response.json(); + + // Close modal and reset state + this.showTemplateModal = false; + this.selectedTemplate = ''; + this.newTemplateNoteName = ''; + + // Reload notes and open the new note + await this.loadNotes(); + await this.loadNote(data.path); + + } catch (error) { + ErrorHandler.handle('create note from template', error); + } + }, + // Clear all tag filters clearTagFilters() { this.selectedTags = []; @@ -908,17 +983,11 @@ function noteApp() {
- + title="Add item here" + >+
-
+
- - -
-
- - -
-
@@ -1141,6 +1110,22 @@