Add sorting by date or name

Adds a sort dropdown offering newest/oldest first and name A-Z/Z-A.
Sorting applies to plain listings, text search, and semantic search
results, and the sidebar facet links preserve the current choice.
Invalid sort values fall back to the default.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Erik Thiele
2026-07-19 22:05:22 +02:00
parent 6a35769943
commit 148074e24c
3 changed files with 69 additions and 17 deletions

View File

@@ -75,6 +75,28 @@ def facets(db: Session, user: User) -> dict:
}
# Auswahl für die Sortierung: Wert -> Beschriftung in der Oberfläche.
SORT_OPTIONS = {
"date_desc": "Neueste zuerst",
"date_asc": "Älteste zuerst",
"name_asc": "Name AZ",
"name_desc": "Name ZA",
}
DEFAULT_SORT = "date_desc"
def sort_links(links: list[Link], sort: str) -> list[Link]:
if sort == "date_asc":
return sorted(links, key=lambda link: link.created_at)
if sort == "name_asc":
return sorted(links, key=lambda link: (link.title or link.url).lower())
if sort == "name_desc":
return sorted(
links, key=lambda link: (link.title or link.url).lower(), reverse=True
)
return sorted(links, key=lambda link: link.created_at, reverse=True)
def query_links(
db: Session,
user: User,
@@ -82,6 +104,7 @@ def query_links(
category: str = "",
manufacturer: str = "",
semantic: bool = False,
sort: str = DEFAULT_SORT,
) -> list[Link]:
stmt = select(Link).where(Link.user_id == user.id)
if category:
@@ -92,18 +115,18 @@ def query_links(
q = (q or "").strip()
if not q:
return links
return sort_links(links, sort)
if semantic and config.AI_ENABLED:
query_vec = ai.embed(q)
ranked = search.cosine_rank(query_vec, links)
result = [link for link, score in ranked if score > 0.20][:50]
if result:
return result
return sort_links(result, sort)
# Fallback auf Textsuche, falls keine Embeddings vorhanden sind.
ql = q.lower()
return [
matches = [
link
for link in links
if ql
@@ -111,6 +134,7 @@ def query_links(
[link.title, link.summary, link.tags, link.category, link.manufacturer]
).lower()
]
return sort_links(matches, sort)
# ---------------------------------------------------------------------------
@@ -192,14 +216,17 @@ def index(
category: str = "",
manufacturer: str = "",
semantic: str = "",
sort: str = DEFAULT_SORT,
db: Session = Depends(get_db),
):
user = current_user(request, db)
if not user:
return RedirectResponse("/login", status_code=303)
if sort not in SORT_OPTIONS:
sort = DEFAULT_SORT
is_semantic = semantic in ("1", "on", "true")
links = query_links(db, user, q, category, manufacturer, is_semantic)
links = query_links(db, user, q, category, manufacturer, is_semantic, sort)
return templates.TemplateResponse(
"index.html",
{
@@ -212,6 +239,8 @@ def index(
"active_manufacturer": manufacturer,
"semantic": is_semantic,
"ai_enabled": config.AI_ENABLED,
"sort": sort,
"sort_options": SORT_OPTIONS,
},
)
@@ -223,14 +252,17 @@ def search_links(
category: str = "",
manufacturer: str = "",
semantic: str = "",
sort: str = DEFAULT_SORT,
db: Session = Depends(get_db),
):
user = current_user(request, db)
if not user:
return Response(status_code=401, headers={"HX-Redirect": "/login"})
if sort not in SORT_OPTIONS:
sort = DEFAULT_SORT
is_semantic = semantic in ("1", "on", "true")
links = query_links(db, user, q, category, manufacturer, is_semantic)
links = query_links(db, user, q, category, manufacturer, is_semantic, sort)
return templates.TemplateResponse(
"_links_list.html", {"request": request, "links": links}
)