feat(ui): redesign to match design handoff prototype #9
+45
-11
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user