feat: add full-text search with FTS5 index and search endpoint for notes
This commit is contained in:
+68
-3
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user