fix(ui): tag counts, j/k nav, stream layout, search alignment
- Tag rail counts now reflect cards-only when in cards view (ListTags accepts cardsOnly filter, JS passes it per view) - j/k navigation scoped to visible (intent/search filtered) list - scrollSelectedIntoView works in both stream and cards view - Entity items wrap title/desc/preview in .entity-content flex container so tags/pills align right consistently - Title no longer eaten by description/body (flex-shrink + min-width) - Search bar centered in header with margin auto - switchView awaits loadEntities+loadTags to fix stale intent counts
This commit is contained in:
+31
-12
@@ -110,8 +110,10 @@
|
||||
});
|
||||
return resp.json();
|
||||
},
|
||||
async listTags() {
|
||||
const resp = await fetch('/api/tags');
|
||||
async listTags(params = {}) {
|
||||
const q = new URLSearchParams();
|
||||
if (params.cards_only) q.set('cards_only', 'true');
|
||||
const resp = await fetch('/api/tags?' + q);
|
||||
return resp.json();
|
||||
},
|
||||
};
|
||||
@@ -653,9 +655,9 @@
|
||||
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>${descSnip}${preview}`;
|
||||
label = `<span class="entity-content"><span class="entity-title">${escHtml(e.title)}</span>${descSnip}${preview}</span>`;
|
||||
} else {
|
||||
label = `<span class="entity-body">${escHtml(e.body)}</span>${descSnip}`;
|
||||
label = `<span class="entity-content"><span class="entity-body">${escHtml(e.body)}</span>${descSnip}</span>`;
|
||||
}
|
||||
|
||||
return `<div class="entity-item${selected}${isCard}" data-index="${idx}" data-id="${e.id}">
|
||||
@@ -1124,6 +1126,7 @@
|
||||
state.selectedIndex = -1;
|
||||
renderEntityList();
|
||||
renderDetailPane();
|
||||
renderTagRail();
|
||||
}
|
||||
|
||||
async function loadMore() {
|
||||
@@ -1168,7 +1171,9 @@
|
||||
}
|
||||
|
||||
async function loadTags() {
|
||||
state.tags = await api.listTags();
|
||||
const params = {};
|
||||
if (state.view === 'cards') params.cards_only = true;
|
||||
state.tags = await api.listTags(params);
|
||||
renderTagRail();
|
||||
}
|
||||
|
||||
@@ -1178,10 +1183,10 @@
|
||||
state.selectedIndex = -1;
|
||||
$$('.nav-btn').forEach(b => b.classList.toggle('active', b.dataset.view === view));
|
||||
window.location.hash = view === 'cards' ? '/cards' : '/';
|
||||
loadEntities();
|
||||
renderMonthNav();
|
||||
renderTagRail();
|
||||
renderCaptureBar();
|
||||
loadEntities();
|
||||
loadTags();
|
||||
}
|
||||
|
||||
// ========== Toast ==========
|
||||
@@ -1430,16 +1435,30 @@
|
||||
const sel = state.entities[state.selectedIndex];
|
||||
|
||||
switch (ev.key) {
|
||||
case 'j':
|
||||
case 'j': {
|
||||
ev.preventDefault();
|
||||
selectEntity(Math.min(state.selectedIndex + 1, state.entities.length - 1));
|
||||
const visible = filterBySearch(state.entities);
|
||||
const sel = state.entities[state.selectedIndex];
|
||||
const curPos = sel ? visible.indexOf(sel) : -1;
|
||||
const nextPos = Math.min(curPos + 1, visible.length - 1);
|
||||
if (visible.length > 0 && nextPos >= 0) {
|
||||
selectEntity(state.entities.indexOf(visible[nextPos]));
|
||||
}
|
||||
scrollSelectedIntoView();
|
||||
break;
|
||||
case 'k':
|
||||
}
|
||||
case 'k': {
|
||||
ev.preventDefault();
|
||||
selectEntity(Math.max(state.selectedIndex - 1, 0));
|
||||
const visible = filterBySearch(state.entities);
|
||||
const sel = state.entities[state.selectedIndex];
|
||||
const curPos = sel ? visible.indexOf(sel) : -1;
|
||||
const prevPos = Math.max(curPos - 1, 0);
|
||||
if (visible.length > 0) {
|
||||
selectEntity(state.entities.indexOf(visible[prevPos]));
|
||||
}
|
||||
scrollSelectedIntoView();
|
||||
break;
|
||||
}
|
||||
case 'n':
|
||||
ev.preventDefault();
|
||||
$('#capture-input').focus();
|
||||
@@ -1485,7 +1504,7 @@
|
||||
});
|
||||
|
||||
function scrollSelectedIntoView() {
|
||||
const el = $(`.entity-item[data-index="${state.selectedIndex}"]`);
|
||||
const el = $(`.entity-item[data-index="${state.selectedIndex}"], .card-row[data-index="${state.selectedIndex}"]`);
|
||||
if (el) el.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ nav { display: flex; gap: 2px; }
|
||||
.header-search {
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#search-input {
|
||||
@@ -363,6 +364,14 @@ main {
|
||||
.glyph-decision { color: var(--note); }
|
||||
.glyph-link { color: var(--event); }
|
||||
|
||||
.entity-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.entity-title {
|
||||
font-family: var(--sans);
|
||||
font-size: 12px;
|
||||
@@ -370,6 +379,8 @@ main {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-shrink: 1;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.entity-desc {
|
||||
@@ -381,6 +392,8 @@ main {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-left: 8px;
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.entity-preview {
|
||||
@@ -391,10 +404,13 @@ main {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-left: 8px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.entity-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
|
||||
Reference in New Issue
Block a user