Kritische Bugfixes: Export/CA-Download (Service-Worker), Speichern-Bug bei Diensten/Lesezeichen, Dark-Mode-Direktlink, Favicon-Mixed-Content-Proxy, Live-Update Zuletzt-besucht, Kategorie-Farb-Picker, Startseite-Nav

This commit is contained in:
2026-07-20 13:02:58 +02:00
parent 96cf29fef4
commit cc4b285aea
13 changed files with 286 additions and 65 deletions

View File

@@ -18,6 +18,18 @@ const SIZE_CLASSES: Record<NonNullable<FaviconProps["size"]>, string> = {
* einem fehlgeschlagenen Ladeversuch (kaputte URL, 404, CORS) wird
* stattdessen der erste Buchstabe des Namens gezeigt.
*/
/**
* Externe/Geräte-Favicons laufen über den Backend-Proxy (/api/favicon-proxy):
* Ohne das würden auf der HTTPS-Seite viele http://-Favicons (typisch für
* Homelab-Geräte ohne eigenes TLS) vom Browser als "Mixed Content" blockiert
* und nie angezeigt - das Backend selbst fetcht sie ohne diese Einschränkung.
* Data-URLs (z. B. selbst hochgeladene Icons) werden direkt durchgereicht.
*/
function resolveFaviconSrc(src: string): string {
if (src.startsWith("data:")) return src;
return `/api/favicon-proxy?url=${encodeURIComponent(src)}`;
}
export function Favicon({ src, fallbackLetter, size = "md" }: FaviconProps) {
const dimension = SIZE_CLASSES[size];
const [failed, setFailed] = useState(false);
@@ -46,7 +58,7 @@ export function Favicon({ src, fallbackLetter, size = "md" }: FaviconProps) {
bg-white p-0.5 ring-1 ring-black/5`}
>
<img
src={src}
src={resolveFaviconSrc(src)}
alt=""
className="h-full w-full object-contain"
onError={() => setFailed(true)}

View File

@@ -72,7 +72,7 @@ export function FavoritesBar({
return (
<div>
{label ? (
<div className="mb-1.5 text-center text-xs font-medium uppercase tracking-wide text-black/30 dark:text-white/30">
<div className="mb-2 text-center text-xs font-medium uppercase tracking-wide text-black/30 dark:text-white/30">
{label}
</div>
) : null}
@@ -98,8 +98,8 @@ export function FavoritesBar({
: `${item.displayName} (${item.hostname})`
}
style={ringColor ? { boxShadow: `0 0 0 2px ${ringColor}` } : undefined}
className={`flex h-10 w-10 items-center justify-center rounded-full border
border-black/10 bg-white/70 transition-colors hover:bg-black/5
className={`flex h-11 w-11 items-center justify-center rounded-xl border
border-black/10 bg-white/70 shadow-sm transition-colors hover:bg-black/5
dark:border-white/10 dark:bg-white/5 dark:hover:bg-white/10 ${
onReorder ? "cursor-grab active:cursor-grabbing" : ""
} ${draggedId === item.id ? "opacity-40" : ""}`}

View File

@@ -3,6 +3,8 @@ import { forwardRef, type InputHTMLAttributes } from "react";
export interface SearchInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
/** Wird links im Suchfeld angezeigt, z. B. ein Tastaturkürzel-Hinweis. */
hint?: string;
/** Zeigt ein X zum Leeren des Felds, sobald Text eingegeben wurde. */
onClear?: () => void;
}
/**
@@ -10,7 +12,9 @@ export interface SearchInputProps extends Omit<InputHTMLAttributes<HTMLInputElem
* Bewusst schlicht gehalten: großer Text, viel Weißraum, keine Ablenkung.
*/
export const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
({ hint, className = "", ...props }, ref) => {
({ hint, onClear, className = "", ...props }, ref) => {
const hasValue = typeof props.value === "string" && props.value.length > 0;
return (
<div
className={`flex items-center gap-3 rounded-2xl border border-black/10 bg-white/80
@@ -40,6 +44,30 @@ export const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
dark:text-white dark:placeholder:text-white/30"
{...props}
/>
{hasValue && onClear ? (
<button
type="button"
onClick={onClear}
aria-label="Suche leeren"
className="shrink-0 rounded-full p-1 text-black/30 transition-colors
hover:bg-black/5 hover:text-black/60 dark:text-white/30
dark:hover:bg-white/10 dark:hover:text-white/60"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
) : null}
{hint ? (
<span className="shrink-0 rounded-md border border-black/10 px-1.5 py-0.5 text-xs
text-black/40 dark:border-white/10 dark:text-white/40">