diff --git a/CLAUDE.md b/CLAUDE.md index b3b11fd..94ab0f1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -66,11 +66,22 @@ The extension exposes these REST endpoints: - Content persistence across sidebar open/close cycles - Real-time tab rendering on note operations - Button layout: Save/Rename/LoRA submenu on left, Refresh/Delete on right +- **Search Functionality**: Live search through note content with highlighted results and line navigation - **Unsaved Changes Tracking**: Visual indicators (*) for modified notes - **Content Preservation**: Tab switching preserves unsaved content - **Change Detection**: Automatic tracking of modifications vs saved state - **Append Integration**: Submenu with three formatting options for appending LoRA lists (extensible for other content types) +### Search Functionality +- **Search Input**: Located below button container with "Search lines..." placeholder +- **Live Search**: Results update as you type, case-insensitive matching +- **Split View**: Original textarea remains fully editable while search results appear below +- **Result Navigation**: Click any search result to jump to that line in the main editor +- **Highlighting**: Matching text highlighted with yellow background in search results +- **Clear Button**: PrimeVue `pi-times-circle` icon for quick search clearing +- **Auto-Update**: Search results refresh when switching between notes +- **HTML Safety**: Proper escaping prevents HTML injection in search results + ### Unsaved Changes Behavior - Notes with modifications show asterisk (*) in tab name - Switching tabs preserves unsaved content in inactive notes @@ -99,8 +110,9 @@ No automated tests defined. Manual testing workflow: 2. Verify sidebar tab appears with "Notes" label and book icon 3. Test CRUD operations: create, edit, save, rename, delete notes 4. Verify tab switching and content persistence -5. Test LoRA integration: verify submenu appears on click with three format options -6. Verify unsaved changes tracking with asterisk (*) indicators +5. Test search functionality: enter search terms, verify results highlighting and line navigation +6. Test LoRA integration: verify submenu appears on click with three format options +7. Verify unsaved changes tracking with asterisk (*) indicators ### Development Workflow 1. Modify Python code in `__init__.py` for backend changes @@ -115,17 +127,18 @@ No automated tests defined. Manual testing workflow: - **UI state changes**: Modify `NotesStore` class in `js/stores/NotesStore.js` - **Append functionality changes**: Update `js/components/AppendMenu.js` and related backend endpoints - **Adding new components**: Create new files in `js/components/` and import them in `extension.js` +- **Search functionality changes**: Modify `onSearchChange()` and related methods in `extension.js` - **API changes**: Update `js/api/notesApi.js` static methods and error handling ### File Structure - `notes/` - Storage directory for markdown notes (auto-created) - `js/` - Frontend assets loaded by ComfyUI - - `extension.js` - Main extension entry point (~450 lines) + - `extension.js` - Main extension entry point (~580 lines, includes search functionality) - `stores/NotesStore.js` - State management (~80 lines) - `components/AppendMenu.js` - Append functionality (~170 lines) - `api/notesApi.js` - API service layer (~90 lines) - `utils/validation.js` - Validation utilities (~10 lines) - - `notes-sidebar.css` - UI styling + - `notes-sidebar.css` - UI styling (includes search component styles) - `__init__.py` - Main extension code - `pyproject.toml` - Project metadata diff --git a/js/extension.js b/js/extension.js index cbd413f..89f1e6b 100644 --- a/js/extension.js +++ b/js/extension.js @@ -20,6 +20,8 @@ class NotesSidebar { this.renameBtn = null; this.deleteBtn = null; this.appendMenu = null; + this.searchInput = null; + this.searchResults = null; this.isInitialized = false; this.sidebarElement = null; @@ -47,6 +49,11 @@ class NotesSidebar { if (updates.activeNote || updates.unsavedChanges) { this.updateButtonState(); } + + // Update search results if active note changed and search is active + if (updates.activeNote && this.searchInput && this.searchInput.value.trim()) { + this.onSearchChange(); + } } get notes() { return this.store.state.notes; } @@ -123,6 +130,7 @@ class NotesSidebar { this.createTabsContainer(this.sidebarElement); this.createNoteButtonContainer(this.sidebarElement); + this.createSearchContainer(this.sidebarElement); this.createContentArea(this.sidebarElement); el.appendChild(this.sidebarElement); @@ -208,6 +216,36 @@ class NotesSidebar { parent.appendChild(buttonContainer); } + createSearchContainer(parent) { + const searchContainer = document.createElement('div'); + searchContainer.className = 'notes-search-container'; + + const inputWrapper = document.createElement('div'); + inputWrapper.className = 'notes-search-input-wrapper'; + + this.searchInput = document.createElement('input'); + this.searchInput.type = 'text'; + this.searchInput.className = 'notes-search-input'; + this.searchInput.placeholder = 'Search lines...'; + this.searchInput.addEventListener('input', () => this.onSearchChange()); + + const clearButton = document.createElement('button'); + clearButton.className = 'notes-search-clear'; + clearButton.innerHTML = ''; + clearButton.title = 'Clear search'; + clearButton.addEventListener('click', () => this.clearSearch()); + + this.searchResults = document.createElement('div'); + this.searchResults.className = 'notes-search-results'; + this.searchResults.style.display = 'none'; + + inputWrapper.appendChild(this.searchInput); + inputWrapper.appendChild(clearButton); + searchContainer.appendChild(inputWrapper); + searchContainer.appendChild(this.searchResults); + parent.appendChild(searchContainer); + } + createContentArea(parent) { this.contentArea = document.createElement('div'); this.contentArea.className = 'notes-content-area'; @@ -444,6 +482,92 @@ class NotesSidebar { // Check if content has changed from saved version const hasUnsavedChanges = currentContent !== savedContent; this.store.setUnsavedChanges(this.activeNote, hasUnsavedChanges); + + // Update search results if search is active + if (this.searchInput && this.searchInput.value.trim()) { + this.onSearchChange(); + } + } + + onSearchChange() { + const searchText = this.searchInput.value.trim(); + + if (!searchText) { + // Hide search results when search is empty + this.searchResults.style.display = 'none'; + return; + } + + if (!this.activeNote || !this.textArea.value) { + // No active note or content to search + this.searchResults.style.display = 'none'; + return; + } + + const content = this.textArea.value; + const lines = content.split('\n'); + const matchingLines = []; + + lines.forEach((line, index) => { + if (line.toLowerCase().includes(searchText.toLowerCase())) { + matchingLines.push({ + lineNumber: index + 1, + content: line + }); + } + }); + + if (matchingLines.length === 0) { + this.searchResults.innerHTML = '