// ========================================== // Chat Switchboard – Notes Panel // ========================================== // Notes panel: editor, multi-select, folders, CRUD. var _editingNoteId = null; var _notesSelectMode = false; var _selectedNoteIds = new Set(); var _notesSort = 'updated_desc'; var _noteEditor = null; // CM6 noteEditor instance // ── Notes ───────────────────────────────────── async function openNotes() { // Toggle: if notes is active, close it; otherwise open it if (PanelRegistry.isOpen('notes')) { PanelRegistry.close('notes'); return; } PanelRegistry.open('notes'); _exitSelectMode(); } async function loadNotesList(folder, searchQuery) { const list = document.getElementById('notesList'); list.innerHTML = '
Loading…
'; try { let data; if (searchQuery) { data = await API.searchNotes(searchQuery); const results = data.data || []; if (results.length === 0) { list.innerHTML = '
No results found
'; return; } list.innerHTML = results.map(n => _noteListItem(n, true)).join(''); } else { const folderVal = folder || document.getElementById('notesFolderFilter')?.value || ''; data = await API.listNotes(100, 0, folderVal, '', _notesSort); const notes = data.data || []; if (notes.length === 0) { list.innerHTML = `
${folderVal ? 'No notes in this folder' : 'No notes yet. Create one or ask the AI to save a note.'}
`; return; } list.innerHTML = notes.map(n => _noteListItem(n, false)).join(''); } } catch (e) { list.innerHTML = `
Failed to load: ${esc(e.message)}
`; } } function _noteListItem(note, isSearch) { const tags = (note.tags || []).map(t => `${esc(t)}`).join(''); const folder = note.folder_path && note.folder_path !== '/' ? `${esc(note.folder_path)}` : ''; const preview = isSearch && note.headline ? `
${_highlightHeadline(note.headline)}
` : `
${esc((note.preview || note.content || '').slice(0, 120))}
`; const time = _relativeTime(note.updated_at); const checked = _selectedNoteIds.has(note.id) ? 'checked' : ''; return `
${esc(note.title)} ${time}
${preview}
${folder}${tags}
`; } function _highlightHeadline(headline) { const escaped = esc(headline); return escaped.replace(/\*\*(.+?)\*\*/g, '$1'); } // ── Multi-select ──────────────── function _enterSelectMode() { _notesSelectMode = true; _selectedNoteIds.clear(); document.getElementById('notesSelectionBar').style.display = ''; document.querySelectorAll('.note-select-col').forEach(el => el.style.display = ''); _updateSelectedCount(); } function _exitSelectMode() { _notesSelectMode = false; _selectedNoteIds.clear(); document.getElementById('notesSelectionBar').style.display = 'none'; document.querySelectorAll('.note-select-col').forEach(el => el.style.display = 'none'); document.querySelectorAll('.note-item.selected').forEach(el => el.classList.remove('selected')); document.querySelectorAll('.note-checkbox').forEach(cb => cb.checked = false); const sa = document.getElementById('notesSelectAll'); if (sa) sa.checked = false; } function _toggleNoteSelect(id, forceState) { if (!_notesSelectMode) { _enterSelectMode(); } const has = _selectedNoteIds.has(id); const newState = forceState !== undefined ? forceState : !has; if (newState) _selectedNoteIds.add(id); else _selectedNoteIds.delete(id); const item = document.querySelector(`.note-item[data-note-id="${id}"]`); if (item) { item.classList.toggle('selected', newState); const cb = item.querySelector('.note-checkbox'); if (cb) cb.checked = newState; } _updateSelectedCount(); } function _toggleSelectAll(checked) { document.querySelectorAll('.note-checkbox').forEach(cb => { const id = cb.dataset.id; if (checked) _selectedNoteIds.add(id); else _selectedNoteIds.delete(id); cb.checked = checked; cb.closest('.note-item')?.classList.toggle('selected', checked); }); _updateSelectedCount(); } function _updateSelectedCount() { const el = document.getElementById('notesSelectedCount'); if (el) el.textContent = _selectedNoteIds.size; } async function _bulkDeleteSelected() { if (_selectedNoteIds.size === 0) return; const count = _selectedNoteIds.size; if (!await showConfirm(`Delete ${count} note${count > 1 ? 's' : ''}?`)) return; try { const resp = await API.bulkDeleteNotes([..._selectedNoteIds]); UI.toast(`Deleted ${resp.deleted} note${resp.deleted !== 1 ? 's' : ''}`, 'success'); _exitSelectMode(); await loadNotesList(); await loadNoteFolders(); } catch (e) { UI.toast(e.message, 'error'); } } // ── Folders / Editor / CRUD ───── async function loadNoteFolders() { const sel = document.getElementById('notesFolderFilter'); if (!sel) return; try { const data = await API.listNoteFolders(); const folders = data.folders || []; sel.innerHTML = ''; folders.forEach(f => { sel.innerHTML += ``; }); } catch (e) { /* non-critical */ } } function showNotesList() { document.getElementById('notesListView').style.display = ''; document.getElementById('notesEditorView').style.display = 'none'; const graphView = document.getElementById('notesGraphView'); if (graphView) graphView.style.display = 'none'; _editingNoteId = null; _destroyNoteEditor(); // Reset list to loading state to prevent stale content flash const list = document.getElementById('notesList'); if (list) list.innerHTML = '
Loading…
'; } var _currentNote = null; // cached note for read mode function copyNoteContent() { if (!_currentNote) return; const text = `# ${_currentNote.title || ''}\n\n${_currentNote.content || ''}`; navigator.clipboard.writeText(text).then(() => UI.toast('Copied', 'success')); } async function openNoteEditor(noteId) { document.getElementById('notesListView').style.display = 'none'; document.getElementById('notesEditorView').style.display = ''; if (noteId) { _editingNoteId = noteId; try { _currentNote = await API.getNote(noteId); _populateEditFields(_currentNote); _showNoteReadMode(); } catch (e) { UI.toast('Failed to load note: ' + e.message, 'error'); showNotesList(); } } else { // New note — straight to edit mode _editingNoteId = null; _currentNote = null; _clearEditFields(); _showNoteEditMode(); document.getElementById('noteEditorTitle').focus(); } } function _populateEditFields(note) { document.getElementById('noteEditorTitle').value = note.title || ''; document.getElementById('noteEditorFolder').value = note.folder_path || ''; document.getElementById('noteEditorTags').value = (note.tags || []).join(', '); _setNoteEditorContent(note.content || ''); } function _clearEditFields() { document.getElementById('noteEditorTitle').value = ''; document.getElementById('noteEditorFolder').value = ''; document.getElementById('noteEditorTags').value = ''; _setNoteEditorContent(''); } /** Get content from CM6 editor or fallback textarea */ function _getNoteEditorContent() { if (_noteEditor) return _noteEditor.getValue(); const ta = document.querySelector('#noteEditorContentContainer textarea'); return ta ? ta.value : ''; } /** Set content in CM6 editor or fallback textarea */ function _setNoteEditorContent(text) { if (_noteEditor) { _noteEditor.setValue(text); return; } // Lazy-init CM6 note editor const container = document.getElementById('noteEditorContentContainer'); if (!container) return; if (window.CM?.noteEditor) { container.innerHTML = ''; // clear any previous _noteEditor = CM.noteEditor(container, { value: text, darkMode: document.documentElement.getAttribute('data-theme') !== 'light', onChange: null, // could wire auto-save later onLink: (title) => { _navigateToLinkedNote(title); }, linkCompleter: async (query) => { if (!query || query.length < 1) return []; try { const resp = await API.searchNoteTitles(query, 8); return (resp.data || []).map(n => ({ label: n.title, id: n.id })); } catch { return []; } }, }); } else { // Fallback: plain textarea if (!container.querySelector('textarea')) { container.innerHTML = ''; } const ta = container.querySelector('textarea'); if (ta) ta.value = text; } } /** Navigate to a note by title (from wikilink click) */ async function _navigateToLinkedNote(title) { try { const resp = await API.searchNoteTitles(title, 1); const match = (resp.data || []).find(n => n.title.toLowerCase() === title.toLowerCase()); if (match) { await openNoteEditor(match.id); } else { // Offer to create if (await showConfirm(`Note "${title}" not found. Create it?`)) { const created = await API.createNote(title, `# ${title}\n\n`, '/', []); await openNoteEditor(created.id); } } } catch (e) { UI.toast('Failed to navigate: ' + e.message, 'error'); } } /** Destroy CM6 editor on panel close */ function _destroyNoteEditor() { if (_noteEditor) { _noteEditor.destroy(); _noteEditor = null; } } function _showNoteReadMode() { if (!_currentNote) return; const n = _currentNote; // Title document.getElementById('noteReadTitle').textContent = n.title || ''; // Meta: folder + tags const parts = []; if (n.folder_path && n.folder_path !== '/') parts.push(`${esc(n.folder_path)}`); (n.tags || []).forEach(t => parts.push(`${esc(t)}`)); document.getElementById('noteReadMeta').innerHTML = parts.join(' '); // Rendered markdown content — reuse the chat formatter const noteReadEl = document.getElementById('noteReadContent'); noteReadEl.innerHTML = formatMessage(n.content || ''); if (typeof runExtensionPostRender === 'function') runExtensionPostRender(noteReadEl); // Render [[wikilinks]] as clickable chips in read mode _renderWikilinksInReadMode(noteReadEl); // Show/hide delete document.getElementById('noteDeleteBtn2').style.display = _editingNoteId ? '' : 'none'; // Toggle visibility document.getElementById('noteReadMode').style.display = ''; document.getElementById('noteEditMode').style.display = 'none'; document.getElementById('noteEditBtn').style.display = ''; document.getElementById('notePreviewBtn').style.display = 'none'; // Load backlinks if (_editingNoteId) _loadBacklinks(_editingNoteId); // Show daily note navigation if applicable _updateDailyNav(_currentNote); } function _showNoteEditMode() { document.getElementById('noteReadMode').style.display = 'none'; document.getElementById('noteEditMode').style.display = ''; document.getElementById('noteDeleteBtn').style.display = _editingNoteId ? '' : 'none'; document.getElementById('noteCancelEditBtn').style.display = _editingNoteId ? '' : 'none'; document.getElementById('noteEditBtn').style.display = 'none'; document.getElementById('notePreviewBtn').style.display = ''; } function _showNotePreview() { // Live preview from editor without saving const title = document.getElementById('noteEditorTitle').value.trim(); const content = _getNoteEditorContent(); const folderVal = document.getElementById('noteEditorFolder').value.trim(); const tagsStr = document.getElementById('noteEditorTags').value.trim(); const tags = tagsStr ? tagsStr.split(',').map(t => t.trim()).filter(Boolean) : []; document.getElementById('noteReadTitle').textContent = title || 'Untitled'; const parts = []; if (folderVal) parts.push(`${esc(folderVal)}`); tags.forEach(t => parts.push(`${esc(t)}`)); document.getElementById('noteReadMeta').innerHTML = parts.join(' '); document.getElementById('noteReadContent').innerHTML = formatMessage(content || ''); if (typeof runExtensionPostRender === 'function') runExtensionPostRender(document.getElementById('noteReadContent')); _renderWikilinksInReadMode(document.getElementById('noteReadContent')); document.getElementById('noteReadMode').style.display = ''; document.getElementById('noteEditMode').style.display = 'none'; document.getElementById('noteEditBtn').style.display = ''; document.getElementById('notePreviewBtn').style.display = 'none'; // Keep delete hidden in preview-from-edit document.getElementById('noteDeleteBtn2').style.display = 'none'; } async function saveNote() { const title = document.getElementById('noteEditorTitle').value.trim(); const content = _getNoteEditorContent(); const folder = document.getElementById('noteEditorFolder').value.trim(); const tagsStr = document.getElementById('noteEditorTags').value.trim(); const tags = tagsStr ? tagsStr.split(',').map(t => t.trim()).filter(Boolean) : []; if (!title) { UI.toast('Title is required', 'warning'); return; } if (!content) { UI.toast('Content is required', 'warning'); return; } try { if (_editingNoteId) { const updated = await API.updateNote(_editingNoteId, { title, content, folder_path: folder, tags, mode: 'replace' }); _currentNote = updated; UI.toast('Note updated', 'success'); _showNoteReadMode(); } else { const created = await API.createNote(title, content, folder, tags); _editingNoteId = created.id; _currentNote = created; UI.toast('Note created', 'success'); _showNoteReadMode(); } // Invalidate graph cache if (typeof invalidateNoteGraph === 'function') invalidateNoteGraph(); } catch (e) { UI.toast(e.message, 'error'); } } async function deleteNote() { if (!_editingNoteId) return; if (!await showConfirm('Delete this note?')) return; try { await API.deleteNote(_editingNoteId); UI.toast('Note deleted', 'success'); if (typeof invalidateNoteGraph === 'function') invalidateNoteGraph(); showNotesList(); await loadNotesList(); await loadNoteFolders(); } catch (e) { UI.toast(e.message, 'error'); } } // ── Backlinks ─────────────────── async function _loadBacklinks(noteId) { const container = document.getElementById('noteBacklinks'); const listEl = document.getElementById('noteBacklinksList'); const countEl = document.getElementById('noteBacklinksCount'); if (!container || !listEl || !countEl) return; try { const resp = await API.getNoteBacklinks(noteId); const links = resp.data || []; countEl.textContent = links.length; container.style.display = links.length > 0 ? '' : 'none'; listEl.innerHTML = links.map(n => ` `).join(''); } catch { container.style.display = 'none'; } } function toggleBacklinks() { const list = document.getElementById('noteBacklinksList'); if (list) list.style.display = list.style.display === 'none' ? '' : 'none'; } // ── Wikilink Read-Mode Rendering ── function _renderWikilinksInReadMode(containerEl) { if (!containerEl) return; const html = containerEl.innerHTML; let transclusionId = 0; // Replace [[Title|display]] and [[Title]] with clickable spans // For ![[transclusion]], render a placeholder that gets filled async const rendered = html.replace( /(!?)\[\[([^\]|]+?)(?:\|([^\]]+?))?\]\]/g, (match, bang, title, display) => { const titleTrimmed = title.trim(); const displayText = display ? display.trim() : titleTrimmed; if (bang === '!') { const tid = `transclusion-${++transclusionId}`; return `
↗ ${esc(displayText)}
Loading…
`; } return `${esc(displayText)}`; } ); containerEl.innerHTML = rendered; // Async: resolve and embed transclusion content _resolveTransclusions(containerEl); } /** Fetch content for each ![[embed]] placeholder — max depth 1 (no recursive transclusion) */ async function _resolveTransclusions(containerEl) { const embeds = containerEl.querySelectorAll('.transclusion-embed'); if (!embeds.length) return; // Cache to avoid duplicate fetches in same note const cache = {}; for (const el of embeds) { const title = el.dataset.title; const contentEl = el.querySelector('.transclusion-content'); if (!title || !contentEl) continue; try { let note = cache[title.toLowerCase()]; if (!note) { const resp = await API.searchNoteTitles(title, 1); const match = (resp.data || []).find(n => n.title.toLowerCase() === title.toLowerCase()); if (!match) { contentEl.innerHTML = 'Note not found'; continue; } note = await API.getNote(match.id); cache[title.toLowerCase()] = note; } // Render embedded content (no recursive transclusion — strip ![[]] ) const safeContent = (note.content || '').replace(/!\[\[[^\]]+\]\]/g, (m) => { const inner = m.match(/!\[\[([^\]|]+)/)?.[1]?.trim() || ''; return `[Embedded: ${inner}]`; }); contentEl.innerHTML = formatMessage(safeContent); if (typeof runExtensionPostRender === 'function') runExtensionPostRender(contentEl); } catch { contentEl.innerHTML = 'Failed to load'; } } } // ── Daily Notes ───────────────── async function openDailyNote() { const today = new Date().toISOString().slice(0, 10); const title = `Daily — ${today}`; const folder = '/daily/'; try { const resp = await API.searchNoteTitles(title, 1); const existing = (resp.data || []).find( n => n.title.toLowerCase() === title.toLowerCase() ); if (existing) { await openNoteEditor(existing.id); } else { const content = `# ${today}\n\n## Tasks\n\n- [ ] \n\n## Notes\n\n`; const created = await API.createNote(title, content, folder, ['daily']); await openNoteEditor(created.id); } } catch (e) { UI.toast('Failed to open daily note: ' + e.message, 'error'); } } /** Detect if current note is a daily note */ function _isDailyNote(note) { return note && /^Daily — \d{4}-\d{2}-\d{2}$/.test(note.title); } /** Show prev/next nav for daily notes */ function _updateDailyNav(note) { let nav = document.getElementById('noteDailyNav'); if (!_isDailyNote(note)) { if (nav) nav.style.display = 'none'; return; } // Ensure nav element exists if (!nav) { const toolbar = document.querySelector('.notes-editor-toolbar'); if (!toolbar) return; nav = document.createElement('div'); nav.id = 'noteDailyNav'; nav.className = 'note-daily-nav'; nav.innerHTML = ` `; toolbar.after(nav); document.getElementById('dailyPrevBtn')?.addEventListener('click', () => _navigateDaily(-1)); document.getElementById('dailyNextBtn')?.addEventListener('click', () => _navigateDaily(1)); } nav.style.display = ''; const dateStr = note.title.replace('Daily — ', ''); document.getElementById('dailyDateLabel').textContent = dateStr; // Disable "Next" if it's today or future const today = new Date().toISOString().slice(0, 10); document.getElementById('dailyNextBtn').disabled = dateStr >= today; } /** Navigate to prev/next daily note */ async function _navigateDaily(offset) { if (!_currentNote || !_isDailyNote(_currentNote)) return; const dateStr = _currentNote.title.replace('Daily — ', ''); const d = new Date(dateStr + 'T12:00:00'); // noon to avoid DST edge d.setDate(d.getDate() + offset); const newDateStr = d.toISOString().slice(0, 10); const title = `Daily — ${newDateStr}`; try { const resp = await API.searchNoteTitles(title, 1); const existing = (resp.data || []).find( n => n.title.toLowerCase() === title.toLowerCase() ); if (existing) { await openNoteEditor(existing.id); } else { // Create the daily note for that day const content = `# ${newDateStr}\n\n## Tasks\n\n- [ ] \n\n## Notes\n\n`; const created = await API.createNote(title, content, '/daily/', ['daily']); await openNoteEditor(created.id); } } catch (e) { UI.toast('Failed to navigate: ' + e.message, 'error'); } } // ── Save-to-Note (from chat messages) ── async function saveMessageToNote(msgIndex) { const chat = App.chats?.find(c => c.id === App.activeId); const msg = chat?.messages?.[msgIndex]; if (!msg) return; // Check for text selection within the message element const sel = window.getSelection(); let content = msg.content; // Strip thinking/reasoning blocks — only the response matters in notes content = content.replace(/<(?:thinking|think)>[\s\S]*?<\/(?:thinking|think)>/gi, '').trim(); const msgId = msg.id || ''; const msgEl = msgId ? document.querySelector(`.message[data-msg-id="${msgId}"] .msg-text`) : null; if (sel && sel.rangeCount > 0 && msgEl?.contains(sel.anchorNode)) { const selected = sel.toString().trim(); if (selected) content = selected; } // Extract title from first line const firstLine = content.split('\n')[0].replace(/^#+\s*/, '').trim(); const defaultTitle = firstLine.slice(0, 60) || 'Chat excerpt'; // Show save-to-note modal _showSaveToNoteModal({ content, sourceChannelId: App.activeId || '', sourceMessageId: msg.id || '', defaultTitle, }); } /** Modal for save-to-note with create/append options */ function _showSaveToNoteModal(opts) { // Remove existing modal if any document.getElementById('saveToNoteModal')?.remove(); const modal = document.createElement('div'); modal.id = 'saveToNoteModal'; modal.className = 'modal-overlay active'; modal.innerHTML = ` `; document.body.appendChild(modal); let selectedAppendNote = null; // Toggle create vs append modal.querySelectorAll('input[name="saveNoteMode"]').forEach(radio => { radio.addEventListener('change', (e) => { const isAppend = e.target.value === 'append'; document.getElementById('saveNoteCreateFields').style.display = isAppend ? 'none' : ''; document.getElementById('saveNoteAppendFields').style.display = isAppend ? '' : 'none'; document.getElementById('saveNoteConfirmBtn').textContent = isAppend ? 'Append' : 'Create'; selectedAppendNote = null; document.getElementById('saveNoteSearchResults').innerHTML = ''; }); }); // Append search with debounce let _searchTimer; document.getElementById('saveNoteSearch')?.addEventListener('input', (e) => { clearTimeout(_searchTimer); const q = e.target.value.trim(); selectedAppendNote = null; _searchTimer = setTimeout(async () => { const resultsEl = document.getElementById('saveNoteSearchResults'); if (q.length < 2) { resultsEl.innerHTML = ''; return; } try { const resp = await API.searchNoteTitles(q, 8); const notes = resp.data || []; resultsEl.innerHTML = notes.map(n => `
${esc(n.title)}
` ).join('') || '
No results
'; resultsEl.querySelectorAll('.save-note-result').forEach(el => { el.addEventListener('click', () => { resultsEl.querySelectorAll('.save-note-result').forEach(r => r.classList.remove('selected')); el.classList.add('selected'); selectedAppendNote = { id: el.dataset.id, title: el.dataset.title }; document.getElementById('saveNoteSearch').value = el.dataset.title; }); }); } catch { resultsEl.innerHTML = '
Search failed
'; } }, 250); }); // Cancel document.getElementById('saveNoteCancelBtn').addEventListener('click', () => modal.remove()); modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); }); // Confirm document.getElementById('saveNoteConfirmBtn').addEventListener('click', async () => { const isAppend = modal.querySelector('input[name="saveNoteMode"]:checked')?.value === 'append'; try { if (isAppend) { if (!selectedAppendNote) { UI.toast('Select a note to append to', 'warning'); return; } await API.updateNote(selectedAppendNote.id, { content: '\n\n---\n\n' + opts.content, mode: 'append', }); UI.toast(`Appended to "${selectedAppendNote.title}"`, 'success'); } else { const title = document.getElementById('saveNoteTitle').value.trim(); if (!title) { UI.toast('Title is required', 'warning'); return; } const folder = document.getElementById('saveNoteFolder').value.trim(); const tagsStr = document.getElementById('saveNoteTags').value.trim(); const tags = tagsStr ? tagsStr.split(',').map(t => t.trim()).filter(Boolean) : []; await API.createNote(title, opts.content, folder, tags, opts.sourceChannelId, opts.sourceMessageId); UI.toast('Note created', 'success'); } if (typeof invalidateNoteGraph === 'function') invalidateNoteGraph(); modal.remove(); } catch (e) { UI.toast(e.message, 'error'); } }); // Focus title document.getElementById('saveNoteTitle')?.focus(); document.getElementById('saveNoteTitle')?.select(); } // ── Notes Listeners (extracted from initListeners) ── function _registerNotesPanel() { const body = document.getElementById('sidePanelBody'); if (!body || typeof PanelRegistry === 'undefined') return; // ── Create the panel element dynamically ── const el = document.createElement('div'); el.className = 'side-panel-page'; el.id = 'sidePanelNotes'; el.style.display = 'none'; el.innerHTML = `
`; body.appendChild(el); // ── Wire listeners ── // Sidebar trigger (lives outside this panel) document.getElementById('notesBtn')?.addEventListener('click', openNotes); // Toolbar buttons el.querySelector('#notesNewBtn').addEventListener('click', () => openNoteEditor(null)); el.querySelector('#notesGraphBtn').addEventListener('click', () => { if (typeof openNoteGraph === 'function') openNoteGraph(); }); el.querySelector('#notesTodayBtn').addEventListener('click', openDailyNote); el.querySelector('#notesGraphBackBtn').addEventListener('click', () => { if (typeof closeNoteGraph === 'function') closeNoteGraph(); }); el.querySelector('#notesSelectModeBtn').addEventListener('click', () => { if (_notesSelectMode) _exitSelectMode(); else _enterSelectMode(); }); // Editor header buttons el.querySelector('#notesBackBtn').addEventListener('click', () => { showNotesList(); loadNotesList(); loadNoteFolders(); }); el.querySelector('#noteSaveBtn').addEventListener('click', saveNote); el.querySelector('#noteDeleteBtn').addEventListener('click', deleteNote); el.querySelector('#noteDeleteBtn2').addEventListener('click', deleteNote); el.querySelector('#noteEditBtn').addEventListener('click', _showNoteEditMode); el.querySelector('#notePreviewBtn').addEventListener('click', _showNotePreview); el.querySelector('#noteCancelEditBtn').addEventListener('click', () => { if (_currentNote) { _populateEditFields(_currentNote); _showNoteReadMode(); } else showNotesList(); }); // Filters el.querySelector('#notesFolderFilter').addEventListener('change', (e) => { document.getElementById('notesSearchInput').value = ''; _exitSelectMode(); loadNotesList(e.target.value); }); el.querySelector('#notesSortSelect').addEventListener('change', (e) => { _notesSort = e.target.value; _exitSelectMode(); loadNotesList(); }); // Multi-select controls el.querySelector('#notesSelectAll').addEventListener('change', (e) => { _toggleSelectAll(e.target.checked); }); el.querySelector('#notesDeleteSelectedBtn').addEventListener('click', _bulkDeleteSelected); el.querySelector('#notesCancelSelectBtn').addEventListener('click', _exitSelectMode); // Search with debounce let _notesSearchTimer; el.querySelector('#notesSearchInput').addEventListener('input', (e) => { clearTimeout(_notesSearchTimer); const q = e.target.value.trim(); _notesSearchTimer = setTimeout(() => { _exitSelectMode(); if (q.length >= 2) { document.getElementById('notesFolderFilter').value = ''; loadNotesList(null, q); } else if (q.length === 0) { loadNotesList(); } }, 300); }); // ── Register with PanelRegistry ── PanelRegistry.register('notes', { element: el, label: 'Notes', onOpen() { // Ensure notes are loaded whenever the panel opens loadNotesList(); loadNoteFolders(); }, onClose() { _destroyNoteEditor(); }, saveState() { const listView = document.getElementById('notesListView'); const editorView = document.getElementById('notesEditorView'); const graphView = document.getElementById('notesGraphView'); let viewMode = 'list'; if (editorView && editorView.style.display !== 'none') viewMode = 'editor'; if (graphView && graphView.style.display !== 'none') viewMode = 'graph'; return { scrollTop: listView?.scrollTop || 0, editingNoteId: _editingNoteId, viewMode, }; }, restoreState(state) { if (state.scrollTop) { const listView = document.getElementById('notesListView'); if (listView) listView.scrollTop = state.scrollTop; } }, }); } /** @deprecated — listeners are now wired in _registerNotesPanel() */ function _initNotesListeners() { // No-op: kept for backward compatibility with app.js calling sequence. // All wiring moved into _registerNotesPanel(). }