feat(ui): redesign to match design handoff prototype #9
+45
-11
@@ -32,6 +32,7 @@
|
||||
runChecked: new Set(),
|
||||
fillValues: {},
|
||||
fillActive: 0,
|
||||
searchQuery: '',
|
||||
};
|
||||
|
||||
const $ = (sel) => document.querySelector(sel);
|
||||
@@ -406,30 +407,31 @@
|
||||
|
||||
function renderEntityList() {
|
||||
const list = $('#entity-list');
|
||||
const filtered = filterBySearch(state.entities);
|
||||
|
||||
if (state.entities.length === 0) {
|
||||
list.innerHTML = '<div class="detail-empty" style="margin-top:40px">no entities yet</div>';
|
||||
renderCardsHeader(false);
|
||||
if (filtered.length === 0) {
|
||||
list.innerHTML = `<div class="detail-empty" style="margin-top:40px">${state.searchQuery ? 'no matches' : 'no entities yet'}</div>`;
|
||||
renderCardsHeader(state.view === 'cards');
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
if (state.view === 'stream') {
|
||||
renderCardsHeader(false);
|
||||
const groups = groupByDate(state.entities);
|
||||
const groups = groupByDate(filtered);
|
||||
let idx = 0;
|
||||
for (const g of groups) {
|
||||
html += `<div class="date-header">${g.label}</div>`;
|
||||
for (const e of g.entities) {
|
||||
html += renderEntityItem(e, idx);
|
||||
const realIdx = state.entities.indexOf(e);
|
||||
html += renderEntityItem(e, realIdx);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
renderCardsHeader(true);
|
||||
const pinned = state.entities.filter(e => e.pinned);
|
||||
const rest = state.entities.filter(e => !e.pinned);
|
||||
let idx = 0;
|
||||
const pinned = filtered.filter(e => e.pinned);
|
||||
const rest = filtered.filter(e => !e.pinned);
|
||||
if (pinned.length) {
|
||||
html += '<div class="list-sec-lbl">★ pinned</div>';
|
||||
for (const e of pinned) {
|
||||
@@ -889,7 +891,7 @@
|
||||
function startEditBody() {
|
||||
const e = state.entities[state.selectedIndex];
|
||||
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;
|
||||
|
||||
const ta = document.createElement('textarea');
|
||||
@@ -921,8 +923,7 @@
|
||||
function startEditField(field) {
|
||||
const e = state.entities[state.selectedIndex];
|
||||
if (!e) return;
|
||||
const cls = field === 'title' ? '.detail-title' : '.detail-desc';
|
||||
const el = $(`${cls}[data-id="${e.id}"]`);
|
||||
const el = $(`.peek-title[data-id="${e.id}"]`);
|
||||
if (!el || el.tagName === 'INPUT') return;
|
||||
|
||||
const input = document.createElement('input');
|
||||
@@ -1381,6 +1382,39 @@
|
||||
|
||||
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 ==========
|
||||
|
||||
function escHtml(s) {
|
||||
|
||||
Reference in New Issue
Block a user