fix(home): restore tbody binding, sticky mobile delete bar, 20-row recent-first list

- Fix Uncaught ReferenceError: tbody is not defined — the client-side filter
  rewrite dropped the const tbody declaration still used by load-more
- Mobile: Delete Selected becomes a sticky frosted bottom bar (safe-area
  aware); disabled (grayed) until a row is selected, then red
- Mobile: library search input full-width 48px (primary interaction)
- Default list: 20 rows (was 50) sorted by updated_at desc (recent first)
  instead of click_count; load-more keeps paging +20
This commit is contained in:
OpenClaw Sub-agent
2026-08-02 11:35:51 +10:00
parent f3bf4089f6
commit 540733699e
2 changed files with 28 additions and 9 deletions
+21 -2
View File
@@ -208,6 +208,10 @@
.apple-btn-primary:hover { background: var(--apple-blue-hover); color: #fff; } .apple-btn-primary:hover { background: var(--apple-blue-hover); color: #fff; }
.apple-btn-danger { background: transparent; color: var(--apple-red); border: 1px solid rgba(255, 59, 48, 0.4); } .apple-btn-danger { background: transparent; color: var(--apple-red); border: 1px solid rgba(255, 59, 48, 0.4); }
.apple-btn-danger:hover { background: rgba(255, 59, 48, 0.08); } .apple-btn-danger:hover { background: rgba(255, 59, 48, 0.08); }
.apple-btn-danger:disabled {
opacity: 0.45; color: var(--apple-gray); border-color: rgba(0, 0, 0, 0.12);
cursor: default; pointer-events: none; background: transparent;
}
/* ---------- Link library (table) ---------- */ /* ---------- Link library (table) ---------- */
.library-header { .library-header {
@@ -579,7 +583,19 @@
/* ---------- Small screens ---------- */ /* ---------- Small screens ---------- */
@media (max-width: 640px) { @media (max-width: 640px) {
.library-header { padding: 1.35rem 1.1rem 1rem; } .library-header { padding: 1.35rem 1.1rem 1rem; }
.library-footer { padding: 1rem 1.1rem 1.25rem; } .library-footer {
position: sticky; bottom: 0; z-index: 5;
background: rgba(245, 245, 247, 0.88);
-webkit-backdrop-filter: blur(20px) saturate(180%);
backdrop-filter: blur(20px) saturate(180%);
border-top: 1px solid rgba(0, 0, 0, 0.05);
border-radius: 0 0 var(--apple-radius-lg) var(--apple-radius-lg);
margin: 0 -1.1rem -1.25rem;
padding: 0.7rem 1.1rem calc(0.7rem + env(safe-area-inset-bottom, 0px));
}
/* Search takes the full row on mobile (it is the primary interaction) */
.library-filter { flex: 1 1 100%; }
.library-filter input { width: 100%; height: 48px; font-size: 1rem; }
.apple-table thead th, .apple-table tbody td { padding-left: 0.8rem; padding-right: 0.8rem; } .apple-table thead th, .apple-table tbody td { padding-left: 0.8rem; padding-right: 0.8rem; }
.hero-stats { gap: 1.25rem; } .hero-stats { gap: 1.25rem; }
.posts-panel-header { padding: 1.15rem 1.1rem 0.6rem; } .posts-panel-header { padding: 1.15rem 1.1rem 0.6rem; }
@@ -965,7 +981,7 @@
{% endif %} {% endif %}
<div class="library-footer"> <div class="library-footer">
<p class="selection-hint" id="selection-hint" aria-live="polite"></p> <p class="selection-hint" id="selection-hint" aria-live="polite"></p>
<button type="submit" class="apple-btn apple-btn-danger">{% trans "Delete Selected" %}</button> <button type="submit" id="delete-selected-btn" class="apple-btn apple-btn-danger" disabled>{% trans "Delete Selected" %}</button>
</div> </div>
</form> </form>
</section> </section>
@@ -1406,6 +1422,7 @@
/* ---------- Load more links ---------- */ /* ---------- Load more links ---------- */
const loadMoreBtn = document.getElementById('load-more-links'); const loadMoreBtn = document.getElementById('load-more-links');
const tbody = document.getElementById('links-table-body');
if (loadMoreBtn && tbody) { if (loadMoreBtn && tbody) {
loadMoreBtn.addEventListener('click', function () { loadMoreBtn.addEventListener('click', function () {
const offset = loadMoreBtn.dataset.offset; const offset = loadMoreBtn.dataset.offset;
@@ -1444,6 +1461,8 @@
if (!selectionHint) return; if (!selectionHint) return;
const count = document.querySelectorAll('input[name="selected_links"]:checked').length; const count = document.querySelectorAll('input[name="selected_links"]:checked').length;
selectionHint.textContent = count > 0 ? count + ' {% trans "selected" %}' : ''; selectionHint.textContent = count > 0 ? count + ' {% trans "selected" %}' : '';
const delBtn = document.getElementById('delete-selected-btn');
if (delBtn) delBtn.disabled = count === 0;
} }
if (selectAll) { if (selectAll) {
+7 -7
View File
@@ -47,7 +47,7 @@ from .search_views import search_aliases, search
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Number of link rows rendered on the initial home page load / per "Load more" click. # Number of link rows rendered on the initial home page load / per "Load more" click.
LINKS_PAGE_SIZE = 50 LINKS_PAGE_SIZE = 20
def _ordering_for(sort_by, order): def _ordering_for(sort_by, order):
@@ -74,14 +74,14 @@ class LinkListView(ListView):
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
sort_by = self.request.GET.get('sort', 'click_count') sort_by = self.request.GET.get('sort', 'updated_at')
order = self.request.GET.get('order', 'asc') order = self.request.GET.get('order', 'desc')
return queryset.order_by(_ordering_for(sort_by, order)) return queryset.order_by(_ordering_for(sort_by, order))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['current_sort'] = self.request.GET.get('sort', 'click_count') context['current_sort'] = self.request.GET.get('sort', 'updated_at')
context['current_order'] = self.request.GET.get('order', 'asc') context['current_order'] = self.request.GET.get('order', 'desc')
# Modern color palette # Modern color palette
colors = [ colors = [
@@ -128,8 +128,8 @@ def load_more_links(request):
"""Return the next page of link rows (rendered as <tr> HTML) for the """Return the next page of link rows (rendered as <tr> HTML) for the
home page's "Load more" button, so the initial page load doesn't have home page's "Load more" button, so the initial page load doesn't have
to render the entire link table at once.""" to render the entire link table at once."""
sort_by = request.GET.get('sort', 'click_count') sort_by = request.GET.get('sort', 'updated_at')
order = request.GET.get('order', 'asc') order = request.GET.get('order', 'desc')
try: try:
offset = max(int(request.GET.get('offset', 0)), 0) offset = max(int(request.GET.get('offset', 0)), 0)
except (TypeError, ValueError): except (TypeError, ValueError):