// ========================================== // Chat Switchboard – NotePanel Component // ========================================== // v0.30.1: Mountable, self-contained notes panel instances. // Follows the ChatPane pattern: createComponentRegistry + componentMixin. // NotePanel.primary is the backward-compat bridge (singleton used by notes.js shim). // // Exports: window.NotePanel const NotePanel = { ...createComponentRegistry('NotePanel'), create(opts) { const id = opts.id || 'notes-' + Date.now() + '-' + Math.random().toString(36).slice(2, 6); const container = opts.container; if (!container) { console.error('[NotePanel] container is required'); return null; } const instance = componentMixin({ id, container, projectId: opts.projectId || null, // ── State (was module-level in notes.js) ── _editingNoteId: null, _notesSelectMode: false, _selectedNoteIds: new Set(), _notesSort: 'updated_desc', _noteEditor: null, _currentNote: null, _searchTimer: null, _page: 1, _pageSize: 50, _hasMore: false, // ── DOM query helper ── $(sel) { return this.container.querySelector(sel); }, $$(sel) { return this.container.querySelectorAll(sel); }, // ── Notes List ── async loadNotesList(folder, searchQuery, append) { const list = this.$('#notesList'); if (!list) return; if (!append) { list.innerHTML = '
Loading\u2026
'; this._page = 1; } try { let notes, isSearch = false; if (searchQuery) { isSearch = true; const data = await API.searchNotes(searchQuery); notes = data.data || []; this._hasMore = false; } else { const folderVal = folder || this.$('#notesFolderFilter')?.value || ''; const offset = (this._page - 1) * this._pageSize; const data = await API.listNotes(this._pageSize, offset, folderVal, '', this._notesSort); notes = data.data || []; this._hasMore = notes.length >= this._pageSize; } if (!append) list.innerHTML = ''; if (notes.length === 0 && !append) { const folderVal = this.$('#notesFolderFilter')?.value || ''; list.innerHTML = `
${isSearch ? 'No results found' : (folderVal ? 'No notes in this folder' : 'No notes yet. Create one or ask the AI to save a note.')}
`; return; } // Remove old "Load more" button if present list.querySelector('.notes-load-more')?.remove(); list.insertAdjacentHTML('beforeend', notes.map(n => this._noteListItem(n, isSearch)).join('')); // Pagination: "Load more" button if (this._hasMore) { const btn = document.createElement('button'); btn.className = 'btn-small notes-load-more'; btn.textContent = 'Load more\u2026'; btn.style.cssText = 'display:block;margin:8px auto;'; btn.addEventListener('click', () => { this._page++; this.loadNotesList(null, null, true); }); list.appendChild(btn); } } catch (e) { if (!append) list.innerHTML = `
Failed to load: ${esc(e.message)}
`; } }, _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 ? `
${this._highlightHeadline(note.headline)}
` : `
${esc((note.preview || note.content || '').slice(0, 120))}
`; const time = _relativeTime(note.updated_at); const checked = this._selectedNoteIds.has(note.id) ? 'checked' : ''; return `
${esc(note.title)} ${time}
${preview}
${folder}${tags}
`; }, _highlightHeadline(headline) { const escaped = esc(headline); return escaped.replace(/\*\*(.+?)\*\*/g, '$1'); }, // ── Multi-select ── _enterSelectMode() { this._notesSelectMode = true; this._selectedNoteIds.clear(); const bar = this.$('#notesSelectionBar'); if (bar) bar.style.display = ''; this.$$('.note-select-col').forEach(el => el.style.display = ''); this._updateSelectedCount(); }, _exitSelectMode() { this._notesSelectMode = false; this._selectedNoteIds.clear(); const bar = this.$('#notesSelectionBar'); if (bar) bar.style.display = 'none'; this.$$('.note-select-col').forEach(el => el.style.display = 'none'); this.$$('.note-item.selected').forEach(el => el.classList.remove('selected')); this.$$('.note-checkbox').forEach(cb => cb.checked = false); const sa = this.$('#notesSelectAll'); if (sa) sa.checked = false; }, _toggleNoteSelect(noteId, forceState) { if (!this._notesSelectMode) { this._enterSelectMode(); } const has = this._selectedNoteIds.has(noteId); const newState = forceState !== undefined ? forceState : !has; if (newState) this._selectedNoteIds.add(noteId); else this._selectedNoteIds.delete(noteId); const item = this.container.querySelector(`.note-item[data-note-id="${noteId}"]`); if (item) { item.classList.toggle('selected', newState); const cb = item.querySelector('.note-checkbox'); if (cb) cb.checked = newState; } this._updateSelectedCount(); }, _toggleSelectAll(checked) { this.$$('.note-checkbox').forEach(cb => { const cbId = cb.dataset.id; if (checked) this._selectedNoteIds.add(cbId); else this._selectedNoteIds.delete(cbId); cb.checked = checked; cb.closest('.note-item')?.classList.toggle('selected', checked); }); this._updateSelectedCount(); }, _updateSelectedCount() { const el = this.$('#notesSelectedCount'); if (el) el.textContent = this._selectedNoteIds.size; }, async _bulkDeleteSelected() { if (this._selectedNoteIds.size === 0) return; const count = this._selectedNoteIds.size; if (!await showConfirm(`Delete ${count} note${count > 1 ? 's' : ''}?`)) return; try { const resp = await API.bulkDeleteNotes([...this._selectedNoteIds]); UI.toast(`Deleted ${resp.deleted} note${resp.deleted !== 1 ? 's' : ''}`, 'success'); this._exitSelectMode(); await this.loadNotesList(); await this.loadNoteFolders(); } catch (e) { UI.toast(e.message, 'error'); } }, // ── Folders ── async loadNoteFolders() { const sel = this.$('#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 */ } }, // ── View switching ── showNotesList() { const lv = this.$('#notesListView'); const ev = this.$('#notesEditorView'); const gv = this.$('#notesGraphView'); if (lv) lv.style.display = ''; if (ev) ev.style.display = 'none'; if (gv) gv.style.display = 'none'; this._editingNoteId = null; this._destroyNoteEditor(); const list = this.$('#notesList'); if (list) list.innerHTML = '
Loading\u2026
'; }, // ── Note Editor ── copyNoteContent() { if (!this._currentNote) return; const text = `# ${this._currentNote.title || ''}\n\n${this._currentNote.content || ''}`; navigator.clipboard.writeText(text).then(() => UI.toast('Copied', 'success')); }, async openNoteEditor(noteId) { const lv = this.$('#notesListView'); const ev = this.$('#notesEditorView'); if (lv) lv.style.display = 'none'; if (ev) ev.style.display = ''; if (noteId) { this._editingNoteId = noteId; try { this._currentNote = await API.getNote(noteId); this._populateEditFields(this._currentNote); this._showNoteReadMode(); } catch (e) { UI.toast('Failed to load note: ' + e.message, 'error'); this.showNotesList(); } } else { this._editingNoteId = null; this._currentNote = null; this._clearEditFields(); this._showNoteEditMode(); this.$('#noteEditorTitle')?.focus(); } }, _populateEditFields(note) { const t = this.$('#noteEditorTitle'); const f = this.$('#noteEditorFolder'); const g = this.$('#noteEditorTags'); if (t) t.value = note.title || ''; if (f) f.value = note.folder_path || ''; if (g) g.value = (note.tags || []).join(', '); this._setNoteEditorContent(note.content || ''); }, _clearEditFields() { const t = this.$('#noteEditorTitle'); const f = this.$('#noteEditorFolder'); const g = this.$('#noteEditorTags'); if (t) t.value = ''; if (f) f.value = ''; if (g) g.value = ''; this._setNoteEditorContent(''); }, _getNoteEditorContent() { if (this._noteEditor) return this._noteEditor.getValue(); const ta = this.container.querySelector('#noteEditorContentContainer textarea'); return ta ? ta.value : ''; }, _setNoteEditorContent(text) { if (this._noteEditor) { this._noteEditor.setValue(text); return; } const container = this.$('#noteEditorContentContainer'); if (!container) return; if (window.CM?.noteEditor) { container.innerHTML = ''; this._noteEditor = CM.noteEditor(container, { value: text, darkMode: document.documentElement.getAttribute('data-theme') !== 'light', onChange: null, onLink: (title) => { this._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 { if (!container.querySelector('textarea')) { container.innerHTML = ''; } const ta = container.querySelector('textarea'); if (ta) ta.value = text; } }, async _navigateToLinkedNote(title) { try { const resp = await API.searchNoteTitles(title, 1); const match = (resp.data || []).find(n => n.title.toLowerCase() === title.toLowerCase()); if (match) { await this.openNoteEditor(match.id); } else { if (await showConfirm(`Note "${title}" not found. Create it?`)) { const created = await API.createNote(title, `# ${title}\n\n`, '/', []); await this.openNoteEditor(created.id); } } } catch (e) { UI.toast('Failed to navigate: ' + e.message, 'error'); } }, _destroyNoteEditor() { if (this._noteEditor) { this._noteEditor.destroy(); this._noteEditor = null; } }, _showNoteReadMode() { if (!this._currentNote) return; const n = this._currentNote; const titleEl = this.$('#noteReadTitle'); if (titleEl) titleEl.textContent = n.title || ''; const parts = []; if (n.folder_path && n.folder_path !== '/') parts.push(`${esc(n.folder_path)}`); (n.tags || []).forEach(t => parts.push(`${esc(t)}`)); const metaEl = this.$('#noteReadMeta'); if (metaEl) metaEl.innerHTML = parts.join(' '); const noteReadEl = this.$('#noteReadContent'); if (noteReadEl) { noteReadEl.innerHTML = formatMessage(n.content || ''); if (typeof runExtensionPostRender === 'function') runExtensionPostRender(noteReadEl); this._renderWikilinksInReadMode(noteReadEl); } const delBtn = this.$('#noteDeleteBtn2'); if (delBtn) delBtn.style.display = this._editingNoteId ? '' : 'none'; const rm = this.$('#noteReadMode'); const em = this.$('#noteEditMode'); const eb = this.$('#noteEditBtn'); const pb = this.$('#notePreviewBtn'); if (rm) rm.style.display = ''; if (em) em.style.display = 'none'; if (eb) eb.style.display = ''; if (pb) pb.style.display = 'none'; if (this._editingNoteId) this._loadBacklinks(this._editingNoteId); this._updateDailyNav(this._currentNote); }, _showNoteEditMode() { const rm = this.$('#noteReadMode'); const em = this.$('#noteEditMode'); const db = this.$('#noteDeleteBtn'); const cb = this.$('#noteCancelEditBtn'); const eb = this.$('#noteEditBtn'); const pb = this.$('#notePreviewBtn'); if (rm) rm.style.display = 'none'; if (em) em.style.display = ''; if (db) db.style.display = this._editingNoteId ? '' : 'none'; if (cb) cb.style.display = this._editingNoteId ? '' : 'none'; if (eb) eb.style.display = 'none'; if (pb) pb.style.display = ''; }, _showNotePreview() { const title = this.$('#noteEditorTitle')?.value.trim() || ''; const content = this._getNoteEditorContent(); const folderVal = this.$('#noteEditorFolder')?.value.trim() || ''; const tagsStr = this.$('#noteEditorTags')?.value.trim() || ''; const tags = tagsStr ? tagsStr.split(',').map(t => t.trim()).filter(Boolean) : []; const titleEl = this.$('#noteReadTitle'); if (titleEl) titleEl.textContent = title || 'Untitled'; const parts = []; if (folderVal) parts.push(`${esc(folderVal)}`); tags.forEach(t => parts.push(`${esc(t)}`)); const metaEl = this.$('#noteReadMeta'); if (metaEl) metaEl.innerHTML = parts.join(' '); const readEl = this.$('#noteReadContent'); if (readEl) { readEl.innerHTML = formatMessage(content || ''); if (typeof runExtensionPostRender === 'function') runExtensionPostRender(readEl); this._renderWikilinksInReadMode(readEl); } const rm = this.$('#noteReadMode'); const em = this.$('#noteEditMode'); const eb = this.$('#noteEditBtn'); const pb = this.$('#notePreviewBtn'); if (rm) rm.style.display = ''; if (em) em.style.display = 'none'; if (eb) eb.style.display = ''; if (pb) pb.style.display = 'none'; const delBtn = this.$('#noteDeleteBtn2'); if (delBtn) delBtn.style.display = 'none'; }, // ── CRUD ── async saveNote() { const title = this.$('#noteEditorTitle')?.value.trim() || ''; const content = this._getNoteEditorContent(); const folder = this.$('#noteEditorFolder')?.value.trim() || ''; const tagsStr = this.$('#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 (this._editingNoteId) { const updated = await API.updateNote(this._editingNoteId, { title, content, folder_path: folder, tags, mode: 'replace' }); this._currentNote = updated; UI.toast('Note updated', 'success'); this._showNoteReadMode(); } else { const created = await API.createNote(title, content, folder, tags); this._editingNoteId = created.id; this._currentNote = created; UI.toast('Note created', 'success'); this._showNoteReadMode(); } if (typeof invalidateNoteGraph === 'function') invalidateNoteGraph(); } catch (e) { UI.toast(e.message, 'error'); } }, async deleteNote() { if (!this._editingNoteId) return; if (!await showConfirm('Delete this note?')) return; try { await API.deleteNote(this._editingNoteId); UI.toast('Note deleted', 'success'); if (typeof invalidateNoteGraph === 'function') invalidateNoteGraph(); this.showNotesList(); await this.loadNotesList(); await this.loadNoteFolders(); } catch (e) { UI.toast(e.message, 'error'); } }, // ── Backlinks ── async _loadBacklinks(noteId) { const container = this.$('#noteBacklinks'); const listEl = this.$('#noteBacklinksList'); const countEl = this.$('#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'; } }, toggleBacklinks() { const list = this.$('#noteBacklinksList'); if (list) list.style.display = list.style.display === 'none' ? '' : 'none'; }, // ── Wikilink Read-Mode Rendering ── _renderWikilinksInReadMode(containerEl) { if (!containerEl) return; const html = containerEl.innerHTML; let transclusionId = 0; 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\u2026
`; } return `${esc(displayText)}`; } ); containerEl.innerHTML = rendered; this._resolveTransclusions(containerEl); }, async _resolveTransclusions(containerEl) { const embeds = containerEl.querySelectorAll('.transclusion-embed'); if (!embeds.length) return; 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; } 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 openDailyNote() { const today = new Date().toISOString().slice(0, 10); const title = `Daily \u2014 ${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 this.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 this.openNoteEditor(created.id); } } catch (e) { UI.toast('Failed to open daily note: ' + e.message, 'error'); } }, _isDailyNote(note) { return note && /^Daily \u2014 \d{4}-\d{2}-\d{2}$/.test(note.title); }, _updateDailyNav(note) { let nav = this.$('#noteDailyNav'); if (!this._isDailyNote(note)) { if (nav) nav.style.display = 'none'; return; } if (!nav) { const toolbar = this.container.querySelector('.notes-editor-toolbar'); if (!toolbar) return; nav = document.createElement('div'); nav.id = 'noteDailyNav'; nav.className = 'note-daily-nav'; nav.innerHTML = ` `; toolbar.after(nav); nav.querySelector('#dailyPrevBtn')?.addEventListener('click', () => this._navigateDaily(-1)); nav.querySelector('#dailyNextBtn')?.addEventListener('click', () => this._navigateDaily(1)); } nav.style.display = ''; const dateStr = note.title.replace('Daily \u2014 ', ''); const label = nav.querySelector('#dailyDateLabel'); if (label) label.textContent = dateStr; const today = new Date().toISOString().slice(0, 10); const nextBtn = nav.querySelector('#dailyNextBtn'); if (nextBtn) nextBtn.disabled = dateStr >= today; }, async _navigateDaily(offset) { if (!this._currentNote || !this._isDailyNote(this._currentNote)) return; const dateStr = this._currentNote.title.replace('Daily \u2014 ', ''); const d = new Date(dateStr + 'T12:00:00'); d.setDate(d.getDate() + offset); const newDateStr = d.toISOString().slice(0, 10); const title = `Daily \u2014 ${newDateStr}`; try { const resp = await API.searchNoteTitles(title, 1); const existing = (resp.data || []).find( n => n.title.toLowerCase() === title.toLowerCase() ); if (existing) { await this.openNoteEditor(existing.id); } else { const content = `# ${newDateStr}\n\n## Tasks\n\n- [ ] \n\n## Notes\n\n`; const created = await API.createNote(title, content, '/daily/', ['daily']); await this.openNoteEditor(created.id); } } catch (e) { UI.toast('Failed to navigate: ' + e.message, 'error'); } }, // ── Save-to-Note (from chat messages) ── async saveMessageToNote(msgIndex) { const chat = App.chats?.find(c => c.id === App.activeId); const msg = chat?.messages?.[msgIndex]; if (!msg) return; const sel = window.getSelection(); let content = msg.content; 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; } const firstLine = content.split('\n')[0].replace(/^#+\s*/, '').trim(); const defaultTitle = firstLine.slice(0, 60) || 'Chat excerpt'; this._showSaveToNoteModal({ content, sourceChannelId: App.activeId || '', sourceMessageId: msg.id || '', defaultTitle, }); }, _showSaveToNoteModal(opts) { 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; 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 = ''; }); }); 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); }); document.getElementById('saveNoteCancelBtn').addEventListener('click', () => modal.remove()); modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); }); 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'); } }); document.getElementById('saveNoteTitle')?.focus(); document.getElementById('saveNoteTitle')?.select(); }, // ── Compound onclick wrappers (for data-action dispatch) ── _toggleNoteSelectCb(el, noteId) { this._toggleNoteSelect(noteId, el.checked); }, _noteItemClick(noteId) { if (this._notesSelectMode) { this._toggleNoteSelect(noteId); } else { this.openNoteEditor(noteId); } }, // ── Lifecycle ── _cleanup() { this._destroyNoteEditor(); clearTimeout(this._searchTimer); }, }, NotePanel); NotePanel._register(id, instance); return instance; }, }; // ── Self-mount: build DOM + create + wire in one call ── // Canonical entry point for all consumers (notes.js sidebar, editor package, sw.notes()). // Uses the same IDs that NotePanel.create() expects — $() is container-scoped so no collisions. NotePanel.mount = function (container, opts) { const _opts = opts || {}; const el = document.createElement('div'); el.className = 'note-panel-root'; el.innerHTML = '' + '
' + '
' + '' + '' + '' + '' + '
' + '
' + '' + '
' + '
' + '' + '' + '
' + '
' + '' + '
' + '' + '' + '' + ''; container.appendChild(el); // Create NotePanel instance — $() is container-scoped, so the IDs above "just work" const np = this.create({ container: el, projectId: _opts.projectId || null }); // ── Wire all events (same as notes.js _registerNotesPanel) ── const q = (sel) => el.querySelector(sel); q('#notesNewBtn').addEventListener('click', () => np.openNoteEditor(null)); q('#notesTodayBtn').addEventListener('click', () => np.openDailyNote()); q('#notesGraphBtn').addEventListener('click', () => { np.showGraph(); if (typeof openNoteGraph === 'function') openNoteGraph(); if (_opts.onOpenGraph) _opts.onOpenGraph(); }); q('#notesGraphBackBtn').addEventListener('click', () => { np.showNotesList(); if (typeof closeNoteGraph === 'function') closeNoteGraph(); }); q('#notesSelectModeBtn').addEventListener('click', () => { if (np._notesSelectMode) np._exitSelectMode(); else np._enterSelectMode(); }); q('#notesBackBtn').addEventListener('click', () => { np.showNotesList(); np.loadNotesList(); np.loadNoteFolders(); }); q('#noteSaveBtn').addEventListener('click', () => np.saveNote()); q('#noteDeleteBtn').addEventListener('click', () => np.deleteNote()); q('#noteDeleteBtn2').addEventListener('click', () => np.deleteNote()); q('#noteEditBtn').addEventListener('click', () => np._showNoteEditMode()); q('#notePreviewBtn').addEventListener('click', () => np._showNotePreview()); q('#noteCancelEditBtn').addEventListener('click', () => { if (np._currentNote) { np._populateEditFields(np._currentNote); np._showNoteReadMode(); } else np.showNotesList(); }); q('#notesFolderFilter').addEventListener('change', (e) => { np.$('#notesSearchInput').value = ''; np._exitSelectMode(); np.loadNotesList(e.target.value); }); q('#notesSortSelect').addEventListener('change', (e) => { np._notesSort = e.target.value; np._exitSelectMode(); np.loadNotesList(); }); q('#notesSelectAll').addEventListener('change', (e) => np._toggleSelectAll(e.target.checked)); q('#notesDeleteSelectedBtn').addEventListener('click', () => np._bulkDeleteSelected()); q('#notesCancelSelectBtn').addEventListener('click', () => np._exitSelectMode()); let _searchTimer; q('#notesSearchInput').addEventListener('input', (e) => { clearTimeout(_searchTimer); const query = e.target.value.trim(); _searchTimer = setTimeout(() => { np._exitSelectMode(); if (query.length >= 2) { np.$('#notesFolderFilter').value = ''; np.loadNotesList(null, query); } else if (query.length === 0) { np.loadNotesList(); } }, 300); }); // Add showGraph view switcher np.showGraph = function () { const lv = np.$('#notesListView'); const ev = np.$('#notesEditorView'); const gv = np.$('#notesGraphView'); if (lv) lv.style.display = 'none'; if (ev) ev.style.display = 'none'; if (gv) gv.style.display = ''; }; return np; }; // ── Exports ───────────────────────────────── sb.ns('NotePanel', NotePanel);