feat(ui): live parse preview pills + description in list rows

Capture bar shows inline pills as you type — glyph, title, desc,
tags, time, pin, card type. Textarea auto-grows on Shift+Enter.
Preview clears on save. Entity list rows now show description.
This commit is contained in:
2026-05-16 12:01:05 -04:00
parent 97ad71d66b
commit a8ea8f099f
2 changed files with 81 additions and 2 deletions
+41 -2
View File
@@ -418,6 +418,7 @@
: '|title // desc #tag ${slot} 1. step';
bar.innerHTML = `
<div class="cap-preview" id="cap-preview"></div>
<div class="cap-row">
<span class="cap-prompt"></span>
<textarea id="capture-input" rows="1" placeholder="${placeholder}" spellcheck="false"></textarea>
@@ -426,12 +427,45 @@
`;
const input = $('#capture-input');
function autoResize() {
input.style.height = 'auto';
input.style.height = input.scrollHeight + 'px';
}
input.addEventListener('keydown', (ev) => {
if (ev.key === 'Enter' && !ev.shiftKey) {
ev.preventDefault();
handleCapture();
}
});
input.addEventListener('input', () => { autoResize(); updateCapturePreview(input.value); });
}
function updateCapturePreview(val) {
const el = $('#cap-preview');
if (!el) return;
val = val.trim();
if (!val) { el.innerHTML = ''; el.classList.remove('visible'); return; }
const parsed = parseInput(val);
if (!parsed) { el.innerHTML = ''; el.classList.remove('visible'); return; }
const pills = [];
if (parsed.query) {
pills.push('<span class="cap-pill cap-pill-query">search</span>');
} else {
pills.push(`<span class="cap-pill cap-pill-glyph">${escHtml(parsed.glyph)}</span>`);
}
if (parsed.title) pills.push(`<span class="cap-pill cap-pill-title">|${escHtml(parsed.title)}</span>`);
if (parsed.description) pills.push(`<span class="cap-pill cap-pill-desc">${escHtml(parsed.description)}</span>`);
for (const t of (parsed.query ? parsed.filterTags : parsed.tags)) {
pills.push(`<span class="cap-pill cap-pill-tag">#${escHtml(t)}</span>`);
}
if (parsed.timeAnchor) pills.push(`<span class="cap-pill cap-pill-time">@${escHtml(parsed.timeAnchor)}</span>`);
if (parsed.pin) pills.push('<span class="cap-pill cap-pill-pin">pin</span>');
if (parsed.cardSuffix) pills.push(`<span class="cap-pill cap-pill-card">^${escHtml(parsed.cardSuffix)}</span>`);
el.innerHTML = pills.join('');
el.classList.add('visible');
}
async function handleCapture() {
@@ -448,6 +482,8 @@
const searchInput = $('#search-input');
if (searchInput) searchInput.value = parsed.body + (parsed.filterTags.length ? ' ' + parsed.filterTags.map(t => '#' + t).join(' ') : '');
input.value = '';
input.style.height = 'auto';
updateCapturePreview('');
renderEntityList();
return;
}
@@ -465,6 +501,8 @@
await api.createEntity(data);
input.value = '';
input.style.height = 'auto';
updateCapturePreview('');
await loadEntities();
await loadTags();
showToast('captured');
@@ -608,11 +646,12 @@
const cardBadge = e.card_type ? `<span class="card-badge">${e.card_type}</span>` : '';
let label;
const descSnip = e.description ? `<span class="entity-desc">${escHtml(e.description)}</span>` : '';
if (e.title) {
const preview = e.body ? `<span class="entity-preview">${escHtml(e.body)}</span>` : '';
label = `<span class="entity-title">${escHtml(e.title)}</span>${preview}`;
label = `<span class="entity-title">${escHtml(e.title)}</span>${descSnip}${preview}`;
} else {
label = `<span class="entity-body">${escHtml(e.body)}</span>`;
label = `<span class="entity-body">${escHtml(e.body)}</span>${descSnip}`;
}
return `<div class="entity-item${selected}${isCard}" data-index="${idx}" data-id="${e.id}">