Such-Oberfläche (Next.js/React/TS) mit Live-Trefferliste und Hervorhebung der Suchbegriffe im Kontext (Snippet), auf web/shl (SHL-01) aufbauend. - app/api/search/route.ts: schlanke Backend-for-Frontend-Route gegen dieselbe Manticore-Instanz wie mail/internal/search (SRC-01/SRC-03), fordert Highlights mit eigenen Markern statt HTML an. - lib/highlight.ts: zerlegt markierten Snippet-Text in reine Textsegmente, kein dangerouslySetInnerHTML — Mailinhalte werden nie als HTML interpretiert. - app/page.tsx: Sucheingabe, Trefferliste mit <mark>-Hervorhebung, verständlicher Hinweis bei leerem Ergebnis. - app/mail/[messageId]/page.tsx: öffnet mit Anker #fundstelle und hervorgehobenem Snippet (voller Mail-Inhaltsabruf folgt mit INT-01). - lib/contrast.ts: reale WCAG-2.1-Kontrastberechnung. Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-04-PRUEFPROTOKOLL.md): 1. Manueller Test gegen echten next start + live Manticore auf 192.168.1.131: Hervorhebung real bestätigt. 2. lib/highlightColors.test.ts: echte WCAG-Berechnung, Hell 14,29:1, Dunkel 6,43:1 (>= 4.5:1 AA). 3. Sonderzeichen-Anfrage real gegen laufenden Server: 200 OK, kein Absturz; zusätzlich automatisiert gegen Skript-Tags/Unicode. Kein Umbau: mail/internal/*, web/shl, web/retention-admin unverändert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
"use client";
|
|
|
|
import { useSearchParams } from "next/navigation";
|
|
import { splitHighlighted } from "../../../lib/highlight";
|
|
import { HIGHLIGHT_BG_LIGHT, HIGHLIGHT_FG_LIGHT } from "../../../lib/highlightColors";
|
|
|
|
// SRC-04 Akzeptanzkriterium 2: Klick auf einen Suchtreffer öffnet die Mail
|
|
// direkt an der Fundstelle. Der vollständige Mail-Inhaltsabruf (Betreff/
|
|
// Text laden anhand messageId) ist NICHT Bestandteil dieser Kachel — dafür
|
|
// gibt es noch keine HTTP-API (folgt mit INT-01). Bis dahin trägt der
|
|
// Suchtreffer-Link Betreff-/Text-Snippet als Kontext mit, damit die
|
|
// Fundstelle bereits jetzt real anspring- und hervorhebbar ist; die
|
|
// vollständige Mail-Ansicht wird von einer späteren Kachel ergänzt.
|
|
function Highlighted({ text }: { text: string }) {
|
|
return (
|
|
<>
|
|
{splitHighlighted(text).map((segment, idx) =>
|
|
segment.matched ? (
|
|
<mark
|
|
key={idx}
|
|
id={idx === 0 ? "fundstelle" : undefined}
|
|
style={{ background: HIGHLIGHT_BG_LIGHT, color: HIGHLIGHT_FG_LIGHT, padding: "0 2px" }}
|
|
>
|
|
{segment.text}
|
|
</mark>
|
|
) : (
|
|
<span key={idx}>{segment.text}</span>
|
|
)
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default function MailDetailPage({ params }: { params: { messageId: string } }) {
|
|
const searchParams = useSearchParams();
|
|
const subject = searchParams.get("subject") ?? "";
|
|
const snippet = searchParams.get("snippet") ?? "";
|
|
|
|
return (
|
|
<main style={{ maxWidth: 720, margin: "40px auto", padding: "0 16px" }}>
|
|
<p>
|
|
<a href="/">← Zurück zur Suche</a>
|
|
</p>
|
|
<h1>
|
|
{subject ? <Highlighted text={subject} /> : params.messageId}
|
|
</h1>
|
|
{snippet && (
|
|
<p>
|
|
<Highlighted text={snippet} />
|
|
</p>
|
|
)}
|
|
</main>
|
|
);
|
|
}
|