// ========================================== // 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'; // ── Notes ───────────────────────────────────── async function openNotes() { const panel = document.getElementById('sidePanel'); const notesTab = document.getElementById('sidePanelNotes'); // If notes panel is already open and showing, close it if (panel?.classList.contains('open') && notesTab?.style.display !== 'none') { closeSidePanel(); return; } openSidePanel('notes'); _exitSelectMode(); await loadNotesList(); await loadNoteFolders(); } 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'; _editingNoteId = null; } 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(', '); document.getElementById('noteEditorContent').value = note.content || ''; } function _clearEditFields() { document.getElementById('noteEditorTitle').value = ''; document.getElementById('noteEditorFolder').value = ''; document.getElementById('noteEditorTags').value = ''; document.getElementById('noteEditorContent').value = ''; } 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); // 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'; } 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 textarea without saving const title = document.getElementById('noteEditorTitle').value.trim(); const content = document.getElementById('noteEditorContent').value; 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')); 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 = document.getElementById('noteEditorContent').value; 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(); } } 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'); showNotesList(); await loadNotesList(); await loadNoteFolders(); } catch (e) { UI.toast(e.message, 'error'); } } // ── Notes Listeners (extracted from initListeners) ── function _initNotesListeners() { document.getElementById('notesBtn')?.addEventListener('click', openNotes); document.getElementById('notesNewBtn')?.addEventListener('click', () => openNoteEditor(null)); document.getElementById('notesSelectModeBtn')?.addEventListener('click', () => { if (_notesSelectMode) _exitSelectMode(); else _enterSelectMode(); }); document.getElementById('notesBackBtn')?.addEventListener('click', () => { showNotesList(); loadNotesList(); loadNoteFolders(); }); document.getElementById('noteSaveBtn')?.addEventListener('click', saveNote); document.getElementById('noteDeleteBtn')?.addEventListener('click', deleteNote); document.getElementById('noteDeleteBtn2')?.addEventListener('click', deleteNote); document.getElementById('noteEditBtn')?.addEventListener('click', _showNoteEditMode); document.getElementById('noteEditBtn2')?.addEventListener('click', _showNoteEditMode); document.getElementById('notePreviewBtn')?.addEventListener('click', _showNotePreview); document.getElementById('noteCancelEditBtn')?.addEventListener('click', () => { if (_currentNote) { _populateEditFields(_currentNote); _showNoteReadMode(); } else showNotesList(); }); document.getElementById('notesFolderFilter')?.addEventListener('change', (e) => { document.getElementById('notesSearchInput').value = ''; _exitSelectMode(); loadNotesList(e.target.value); }); document.getElementById('notesSortSelect')?.addEventListener('change', (e) => { _notesSort = e.target.value; _exitSelectMode(); loadNotesList(); }); document.getElementById('notesSelectAll')?.addEventListener('change', (e) => { _toggleSelectAll(e.target.checked); }); document.getElementById('notesDeleteSelectedBtn')?.addEventListener('click', _bulkDeleteSelected); document.getElementById('notesCancelSelectBtn')?.addEventListener('click', _exitSelectMode); // Notes search with debounce let _notesSearchTimer; document.getElementById('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); }); }