round68: Spaeter-lesen als Liste mit Datum/Uhrzeit statt Icon-Raster, Suche im Android-Design nutzt jetzt dieselbe Rangierfunktion wie das klassische Design

This commit is contained in:
2026-07-26 22:20:04 +02:00
parent 3e27c54199
commit 2ed39d6865

View File

@@ -10,6 +10,7 @@ import {
faPen, faPen,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import type { Category, Service, Bookmark, SearchResult } from "@launchpad/shared"; import type { Category, Service, Bookmark, SearchResult } from "@launchpad/shared";
import { rankSearchResults } from "@launchpad/shared";
import { ResultsList, Button } from "@launchpad/ui"; import { ResultsList, Button } from "@launchpad/ui";
import { useHomeLayout, type HomeSlot, type ItemRef } from "../hooks/useHomeLayout.js"; import { useHomeLayout, type HomeSlot, type ItemRef } from "../hooks/useHomeLayout.js";
@@ -83,6 +84,14 @@ function slotId(): string {
return `slot-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`; return `slot-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`;
} }
function formatSavedAt(iso: string): string {
const date = new Date(iso);
if (Number.isNaN(date.getTime())) return "";
const datePart = date.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit", year: "2-digit" });
const timePart = date.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
return `${datePart}, ${timePart}`;
}
/** /**
* Reine Funktion, die aus dem Ist-Zustand (Layout) + der abgeschlossenen * Reine Funktion, die aus dem Ist-Zustand (Layout) + der abgeschlossenen
* Ziehgeste das NEUE Layout berechnet - bewusst ohne React-State-Zugriff, * Ziehgeste das NEUE Layout berechnet - bewusst ohne React-State-Zugriff,
@@ -221,7 +230,7 @@ export interface MobileAndroidHomeProps {
categoryColors: Record<string, string>; categoryColors: Record<string, string>;
allSearchItems: SearchResult[]; allSearchItems: SearchResult[];
recentVisits: MinimalVisitItem[]; recentVisits: MinimalVisitItem[];
readLaterItems: Array<{ id: string; url: string; displayName: string; favicon: string | null }>; readLaterItems: Array<{ id: string; url: string; displayName: string; favicon: string | null; savedAt: string }>;
onOpen: (item: OpenTarget) => void; onOpen: (item: OpenTarget) => void;
onToggleFavorite: (item: SearchResult) => void; onToggleFavorite: (item: SearchResult) => void;
onAddReadLater: (url: string) => void; onAddReadLater: (url: string) => void;
@@ -383,12 +392,15 @@ export function MobileAndroidHome({
} }
const searchResults = useMemo(() => { const searchResults = useMemo(() => {
if (query.trim().length === 0) return []; // WICHTIG (Bugfix): vorher stand hier ein eigener, simpler
const q = query.toLowerCase(); // .includes()-Filter, der zusätzlich Geräte komplett ausgeschlossen hat
return allSearchItems // - dadurch fand die Suche im Android-Design weniger als im normalen
.filter((i) => i.kind !== "device") // Design (siehe Bugreport). Jetzt exakt dieselbe Rangier-Funktion wie im
.filter((i) => i.displayName.toLowerCase().includes(q) || i.alias?.some((a) => a.toLowerCase().includes(q))) // klassischen Design (siehe HomePage.tsx), inklusive Geräten - ein Klick
.slice(0, 30); // auf ein Gerät bleibt trotzdem ohne Wirkung (kein eigenes Ziel, siehe
// onOpen/openItem in HomePage.tsx), aber es taucht wie gewohnt als
// Treffer auf.
return rankSearchResults(allSearchItems, query);
}, [query, allSearchItems]); }, [query, allSearchItems]);
const closeSearch = () => { const closeSearch = () => {
@@ -702,17 +714,22 @@ export function MobileAndroidHome({
</Button> </Button>
</form> </form>
{readLaterItems.length > 0 ? ( {readLaterItems.length > 0 ? (
<div className="grid grid-cols-4 gap-x-3 gap-y-5"> <div className="flex flex-col divide-y divide-black/5 dark:divide-white/10">
{readLaterItems.map((item) => ( {[...readLaterItems]
.sort((a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime())
.map((item) => (
<button <button
key={item.id} key={item.id}
type="button" type="button"
onClick={() => onOpen({ url: item.url, id: item.id, kind: "bookmark" })} onClick={() => onOpen({ url: item.url, id: item.id, kind: "bookmark" })}
className="flex flex-col items-center gap-1.5" className="flex items-center gap-3 py-2.5 text-left"
> >
<TileIcon favicon={item.favicon} label={item.displayName} /> <TileIcon favicon={item.favicon} label={item.displayName} size="small" />
<span className="line-clamp-2 w-full text-center text-[11px] leading-tight text-black/70 dark:text-white/70"> <span className="min-w-0 flex-1">
{item.displayName} <span className="block truncate text-sm text-black dark:text-white">{item.displayName}</span>
<span className="block text-[11px] text-black/40 dark:text-white/40">
{formatSavedAt(item.savedAt)}
</span>
</span> </span>
</button> </button>
))} ))}