import { useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { loadHelpSections } from '../lib/help-content'; import { filterSections, renderHelpHtml, type HelpCategory, type HelpSection } from '../lib/help'; const CATEGORY_LABEL_KEY: Record = { basic: 'category.basic', advanced: 'category.advanced', admin: 'category.admin', }; const CATEGORY_ORDER: HelpCategory[] = ['basic', 'advanced', 'admin']; interface HelpPageProps { isAdmin: boolean; onAskAi: () => void; /** Currently selected section id (from URL). */ selectedId?: string; /** Called when the user picks a section; caller writes it to the URL. */ onSelect: (id: string) => void; } export function HelpPage({ isAdmin, onAskAi, selectedId, onSelect }: HelpPageProps) { const { t } = useTranslation('help'); const all = useMemo(() => loadHelpSections(), []); const visible = useMemo( () => all.filter((s) => s.category !== 'admin' || isAdmin), [all, isAdmin], ); const [query, setQuery] = useState(''); const mainRef = useRef(null); const filtered = useMemo(() => filterSections(visible, query), [visible, query]); const selected: HelpSection | undefined = visible.find((s) => s.id === selectedId) ?? visible[0]; const rendered = useMemo( () => (selected ? renderHelpHtml(selected.body) : { html: '', headings: [] }), [selected], ); const categories = CATEGORY_ORDER.filter((c) => c !== 'admin' || isAdmin); function scrollToHeading(id: string) { const el = mainRef.current?.querySelector(`#${CSS.escape(id)}`); el?.scrollIntoView({ behavior: 'smooth', block: 'start' }); } function handleContentClick(e: React.MouseEvent) { const a = (e.target as HTMLElement).closest('a'); if (!a) return; const href = a.getAttribute('href'); if (!href) return; if (href.startsWith('#')) { e.preventDefault(); scrollToHeading(decodeURIComponent(href.slice(1))); return; } const m = /^\.\/\d+-(.+?)\.md(?:#.*)?$/.exec(href); if (m) { const id = m[1]; if (all.some((s) => s.id === id)) { e.preventDefault(); onSelect(id); } } } return (
{/* Left: TOC */} {/* Right: rendered markdown + heading nav */}
{rendered.headings.length > 1 && ( )}
); }