feat: add full-text search with FTS5 index and search endpoint for notes

This commit is contained in:
2025-06-24 14:20:38 +00:00
parent 8e874ba6fe
commit 885c9a37c0
2 changed files with 160 additions and 7 deletions
+68 -3
View File
@@ -79,15 +79,23 @@
<div id="app">
<aside id="sidebar">
<h2>Tags</h2>
<!-- New search box -->
<input
type="text"
id="search"
placeholder="Search notes…"
style="width: calc(100% - 2rem); margin: .5rem 1rem; padding: .5rem;"
/>
<ul id="tag-list"></ul>
</aside>
<main>
<note-compose></note-compose>
<div id="note-grid"></div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!--
<tag-input value="apple, banana, Cherry"></tag-input>
@@ -432,8 +440,65 @@ class NoteCompose extends HTMLElement {
customElements.define('note-compose', NoteCompose);
/* tiny loaders unchanged */
async function loadNotes(tag){ const r=await fetch(tag?`${API_BASE}/notes?tag=${encodeURIComponent(tag)}`:`${API_BASE}/notes`); const ns=r.ok?await r.json():[]; const g=document.getElementById('note-grid'); g.innerHTML=''; ns.forEach(n=>{const c=document.createElement('note-card'); c.data=n; g.appendChild(c);} ); }
async function loadTags(){ const r=await fetch(`${API_BASE}/tags`); const ts=r.ok?await r.json():[]; const l=document.getElementById('tag-list'); l.innerHTML=''; ts.forEach(t=>{const li=document.createElement('li'); li.textContent=t.name||t; li.onclick=()=>{document.querySelectorAll('#tag-list li').forEach(x=>x.classList.toggle('active',x===li)); loadNotes(t.name||t);}; l.appendChild(li);} ); }
async function loadNotes(tag, search) {
let url = API_BASE + '/notes';
const params = [];
if (search) {
params.push(`search=${encodeURIComponent(search)}`);
} else if (tag) {
params.push(`tag=${encodeURIComponent(tag)}`);
}
if (params.length) {
url += '?' + params.join('&');
}
const res = await fetch(url);
const notes = res.ok ? await res.json() : [];
const grid = document.getElementById('note-grid');
grid.innerHTML = '';
notes.forEach(n => {
const card = document.createElement('note-card');
card.data = n;
grid.appendChild(card);
});
}
async function loadTags() {
const r = await fetch(`${API_BASE}/tags`);
const ts = r.ok ? await r.json() : [];
const ul = document.getElementById('tag-list');
ul.innerHTML = '';
ts.forEach(t => {
const li = document.createElement('li');
li.textContent = t.name || t;
li.onclick = () => {
// clear search box & active class
document.getElementById('search').value = '';
document.querySelectorAll('#tag-list li')
.forEach(x => x.classList.toggle('active', x === li));
loadNotes(t.name || t, null);
};
ul.appendChild(li);
});
}
// Wire up search box
document.getElementById('search').addEventListener('input', e => {
const q = e.target.value.trim();
// clear tag selection
document.querySelectorAll('#tag-list li')
.forEach(x => x.classList.remove('active'));
loadNotes(null, q || null);
});
// When notes change, refresh both lists
document.addEventListener('notes-changed', () => {
loadNotes();
loadTags();
});
// Initial load
loadNotes();
loadTags();
document.addEventListener('notes-changed', () => { loadNotes(); loadTags(); });
loadNotes(); loadTags();