Changeset 0.31.0 (#203)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit is contained in:
@@ -32,6 +32,9 @@ const NotePanel = {
|
||||
_noteEditor: null,
|
||||
_currentNote: null,
|
||||
_searchTimer: null,
|
||||
_page: 1,
|
||||
_pageSize: 50,
|
||||
_hasMore: false,
|
||||
|
||||
// ── DOM query helper ──
|
||||
$(sel) { return this.container.querySelector(sel); },
|
||||
@@ -39,33 +42,56 @@ const NotePanel = {
|
||||
|
||||
// ── Notes List ──
|
||||
|
||||
async loadNotesList(folder, searchQuery) {
|
||||
async loadNotesList(folder, searchQuery, append) {
|
||||
const list = this.$('#notesList');
|
||||
if (!list) return;
|
||||
list.innerHTML = '<div class="loading">Loading\u2026</div>';
|
||||
if (!append) {
|
||||
list.innerHTML = '<div class="loading">Loading\u2026</div>';
|
||||
this._page = 1;
|
||||
}
|
||||
|
||||
try {
|
||||
let data;
|
||||
let notes, isSearch = false;
|
||||
if (searchQuery) {
|
||||
data = await API.searchNotes(searchQuery);
|
||||
const results = data.data || [];
|
||||
if (results.length === 0) {
|
||||
list.innerHTML = '<div class="empty-hint">No results found</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = results.map(n => this._noteListItem(n, true)).join('');
|
||||
isSearch = true;
|
||||
const data = await API.searchNotes(searchQuery);
|
||||
notes = data.data || [];
|
||||
this._hasMore = false;
|
||||
} else {
|
||||
const folderVal = folder || this.$('#notesFolderFilter')?.value || '';
|
||||
data = await API.listNotes(100, 0, folderVal, '', this._notesSort);
|
||||
const notes = data.data || [];
|
||||
if (notes.length === 0) {
|
||||
list.innerHTML = `<div class="empty-hint">${folderVal ? 'No notes in this folder' : 'No notes yet. Create one or ask the AI to save a note.'}</div>`;
|
||||
return;
|
||||
}
|
||||
list.innerHTML = notes.map(n => this._noteListItem(n, false)).join('');
|
||||
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 = `<div class="empty-hint">${isSearch ? 'No results found' : (folderVal ? 'No notes in this folder' : 'No notes yet. Create one or ask the AI to save a note.')}</div>`;
|
||||
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) {
|
||||
list.innerHTML = `<div class="empty-hint">Failed to load: ${esc(e.message)}</div>`;
|
||||
if (!append) list.innerHTML = `<div class="empty-hint">Failed to load: ${esc(e.message)}</div>`;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -783,6 +809,168 @@ const NotePanel = {
|
||||
},
|
||||
};
|
||||
|
||||
// ── 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 =
|
||||
'<!-- List view -->' +
|
||||
'<div id="notesListView">' +
|
||||
'<div class="notes-toolbar">' +
|
||||
'<button id="notesNewBtn" class="btn-small" title="New note">+ New</button>' +
|
||||
'<button id="notesTodayBtn" class="btn-small" title="Open daily note">\ud83d\udcc5 Today</button>' +
|
||||
'<button id="notesGraphBtn" class="btn-small" title="Note graph">\ud83d\udd78 Graph</button>' +
|
||||
'<button id="notesSelectModeBtn" class="btn-small" title="Select mode">\u2611 Select</button>' +
|
||||
'</div>' +
|
||||
'<div class="notes-search-row">' +
|
||||
'<input type="text" id="notesSearchInput" class="notes-search-input" placeholder="Search notes\u2026" autocomplete="off">' +
|
||||
'</div>' +
|
||||
'<div class="notes-filter-row">' +
|
||||
'<select id="notesFolderFilter" class="notes-filter-select"><option value="">All folders</option></select>' +
|
||||
'<select id="notesSortSelect" class="notes-filter-select">' +
|
||||
'<option value="updated_desc">Last updated</option>' +
|
||||
'<option value="created_desc">Created</option>' +
|
||||
'<option value="alpha">Alphabetical</option>' +
|
||||
'</select>' +
|
||||
'</div>' +
|
||||
'<div id="notesList" class="notes-list"></div>' +
|
||||
'<div id="notesSelectionBar" class="notes-selection-bar" style="display:none;">' +
|
||||
'<label class="notes-select-all"><input type="checkbox" id="notesSelectAll"> All</label>' +
|
||||
'<span id="notesSelectedCount">0</span> selected' +
|
||||
'<button id="notesDeleteSelectedBtn" class="btn-small btn-danger">Delete</button>' +
|
||||
'<button id="notesCancelSelectBtn" class="btn-small">Cancel</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<!-- Editor view -->' +
|
||||
'<div id="notesEditorView" style="display:none;">' +
|
||||
'<div class="notes-editor-header">' +
|
||||
'<button id="notesBackBtn" class="btn-small" title="Back to list">\u2190 Back</button>' +
|
||||
'<div style="flex:1"></div>' +
|
||||
'<button id="noteEditBtn" class="btn-small" style="display:none;">Edit</button>' +
|
||||
'<button id="notePreviewBtn" class="btn-small" style="display:none;">Preview</button>' +
|
||||
'<button id="noteCancelEditBtn" class="btn-small" style="display:none;">Cancel</button>' +
|
||||
'<button id="noteDeleteBtn" class="btn-small btn-danger" style="display:none;">Delete</button>' +
|
||||
'<button id="noteSaveBtn" class="btn-small btn-primary">Save</button>' +
|
||||
'</div>' +
|
||||
'<div id="noteEditMode" class="notes-editor">' +
|
||||
'<input type="text" id="noteEditorTitle" class="notes-title-input" placeholder="Note title">' +
|
||||
'<div class="notes-meta-row">' +
|
||||
'<input type="text" id="noteEditorFolder" class="notes-folder-input" placeholder="Folder (e.g. work/project)">' +
|
||||
'<input type="text" id="noteEditorTags" class="notes-tags-input" placeholder="Tags (comma-separated)">' +
|
||||
'</div>' +
|
||||
'<div id="noteEditorContentContainer" class="notes-content-container">' +
|
||||
'<textarea id="noteEditorContent" rows="12" class="notes-content-input" placeholder="Write your note in markdown\u2026"></textarea>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div id="noteReadMode" style="display:none;">' +
|
||||
'<div class="notes-read-view">' +
|
||||
'<h3 id="noteReadTitle" class="note-read-title"></h3>' +
|
||||
'<div id="noteReadMeta" class="note-read-meta"></div>' +
|
||||
'<div id="noteReadContent" class="note-read-content msg-content"></div>' +
|
||||
'<button id="noteDeleteBtn2" class="btn-small btn-danger" style="display:none;margin-top:8px;">Delete</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div id="noteBacklinks" class="note-backlinks" style="display:none;">' +
|
||||
'<div class="note-backlinks-header" data-action="toggleBacklinks">' +
|
||||
'Backlinks <span id="noteBacklinksCount" class="badge">0</span>' +
|
||||
'</div>' +
|
||||
'<div id="noteBacklinksList" class="note-backlinks-list"></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<!-- Graph view -->' +
|
||||
'<div id="notesGraphView" class="notes-graph-view" style="display:none;">' +
|
||||
'<div class="notes-graph-toolbar">' +
|
||||
'<button id="notesGraphBackBtn" class="btn-small" title="Back to list">\u2190 Back</button>' +
|
||||
'<span class="notes-graph-stats">' +
|
||||
'<span id="graphNodeCount">0</span> notes \u00b7 <span id="graphEdgeCount">0</span> links' +
|
||||
'</span>' +
|
||||
'<div style="flex:1"></div>' +
|
||||
'<button class="btn-small" data-action="_graphToggleOrphans" title="Toggle orphan nodes">Orphans</button>' +
|
||||
'<button class="btn-small" data-action="_graphResetZoom" title="Reset zoom">Reset</button>' +
|
||||
'</div>' +
|
||||
'<canvas id="noteGraphCanvas" style="width:100%;display:block;"></canvas>' +
|
||||
'</div>';
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user