feat: UI redesign, capture grammar, demo command #14

Merged
lerko merged 23 commits from develop into main 2026-05-16 20:07:28 +00:00
Showing only changes of commit 6802474595 - Show all commits
+45 -11
View File
@@ -32,6 +32,7 @@
runChecked: new Set(), runChecked: new Set(),
fillValues: {}, fillValues: {},
fillActive: 0, fillActive: 0,
searchQuery: '',
}; };
const $ = (sel) => document.querySelector(sel); const $ = (sel) => document.querySelector(sel);
@@ -406,30 +407,31 @@
function renderEntityList() { function renderEntityList() {
const list = $('#entity-list'); const list = $('#entity-list');
const filtered = filterBySearch(state.entities);
if (state.entities.length === 0) { if (filtered.length === 0) {
list.innerHTML = '<div class="detail-empty" style="margin-top:40px">no entities yet</div>'; list.innerHTML = `<div class="detail-empty" style="margin-top:40px">${state.searchQuery ? 'no matches' : 'no entities yet'}</div>`;
renderCardsHeader(false); renderCardsHeader(state.view === 'cards');
return; return;
} }
let html = ''; let html = '';
if (state.view === 'stream') { if (state.view === 'stream') {
renderCardsHeader(false); renderCardsHeader(false);
const groups = groupByDate(state.entities); const groups = groupByDate(filtered);
let idx = 0; let idx = 0;
for (const g of groups) { for (const g of groups) {
html += `<div class="date-header">${g.label}</div>`; html += `<div class="date-header">${g.label}</div>`;
for (const e of g.entities) { for (const e of g.entities) {
html += renderEntityItem(e, idx); const realIdx = state.entities.indexOf(e);
html += renderEntityItem(e, realIdx);
idx++; idx++;
} }
} }
} else { } else {
renderCardsHeader(true); renderCardsHeader(true);
const pinned = state.entities.filter(e => e.pinned); const pinned = filtered.filter(e => e.pinned);
const rest = state.entities.filter(e => !e.pinned); const rest = filtered.filter(e => !e.pinned);
let idx = 0;
if (pinned.length) { if (pinned.length) {
html += '<div class="list-sec-lbl">★ pinned</div>'; html += '<div class="list-sec-lbl">★ pinned</div>';
for (const e of pinned) { for (const e of pinned) {
@@ -889,7 +891,7 @@
function startEditBody() { function startEditBody() {
const e = state.entities[state.selectedIndex]; const e = state.entities[state.selectedIndex];
if (!e) return; if (!e) return;
const el = $(`.detail-body[data-id="${e.id}"]`); const el = $(`.peek-body[data-id="${e.id}"]`);
if (!el || el.tagName === 'TEXTAREA') return; if (!el || el.tagName === 'TEXTAREA') return;
const ta = document.createElement('textarea'); const ta = document.createElement('textarea');
@@ -921,8 +923,7 @@
function startEditField(field) { function startEditField(field) {
const e = state.entities[state.selectedIndex]; const e = state.entities[state.selectedIndex];
if (!e) return; if (!e) return;
const cls = field === 'title' ? '.detail-title' : '.detail-desc'; const el = $(`.peek-title[data-id="${e.id}"]`);
const el = $(`${cls}[data-id="${e.id}"]`);
if (!el || el.tagName === 'INPUT') return; if (!el || el.tagName === 'INPUT') return;
const input = document.createElement('input'); const input = document.createElement('input');
@@ -1381,6 +1382,39 @@
window.addEventListener('hashchange', handleHash); window.addEventListener('hashchange', handleHash);
// ========== Search ==========
const searchInput = $('#search-input');
let searchDebounce = null;
searchInput.addEventListener('input', () => {
clearTimeout(searchDebounce);
searchDebounce = setTimeout(() => {
state.searchQuery = searchInput.value.trim().toLowerCase();
renderEntityList();
}, 150);
});
searchInput.addEventListener('keydown', (ev) => {
if (ev.key === 'Escape') { searchInput.value = ''; state.searchQuery = ''; renderEntityList(); searchInput.blur(); }
});
function filterBySearch(entities) {
if (!state.searchQuery) return entities;
let query = state.searchQuery;
let filterTags = [];
query = query.replace(/#(\S+)/g, (_, tag) => { filterTags.push(tag); return ''; }).trim();
return entities.filter(e => {
if (filterTags.length) {
const eTags = (e.tags || []).map(t => t.toLowerCase());
if (!filterTags.every(ft => eTags.includes(ft))) return false;
}
if (!query) return true;
const haystack = ((e.body || '') + ' ' + (e.title || '') + ' ' + (e.description || '')).toLowerCase();
return haystack.includes(query);
});
}
// ========== Utils ========== // ========== Utils ==========
function escHtml(s) { function escHtml(s) {