diff --git a/frontend/src/components/AppShell.tsx b/frontend/src/components/AppShell.tsx index 75b0706..527ab2f 100644 --- a/frontend/src/components/AppShell.tsx +++ b/frontend/src/components/AppShell.tsx @@ -1,7 +1,7 @@ import { useQuery } from "@tanstack/react-query"; import { LogOut, Menu } from "lucide-react"; import { useEffect, useState } from "react"; -import { Link, NavLink, Outlet, useLocation } from "react-router-dom"; +import { Link, NavLink, Outlet, useLocation, useNavigationType } from "react-router-dom"; import { CommandPalette } from "@/components/CommandPalette"; import { StatusFooter } from "@/components/StatusFooter"; @@ -63,6 +63,13 @@ export function AppShell() { const showSidebarToggle = location.pathname === "/" || location.pathname === "/online"; + const navigationType = useNavigationType(); + useEffect(() => { + // New navigations start at the top; history back/forward keeps position. + if (navigationType === "POP") return; + window.scrollTo({ top: 0 }); + }, [location.pathname, navigationType]); + return (
- {post.description} -
+
+ {inner}
+
+ );
+ case "spoiler":
+ return (
+ + {attr ? ( + + {stripTags(attr)}: + + ) : null} + {renderInline(inner, keyPrefix, extras)} ++ ); + case "color": { + const color = safeColor(attr); + return color ? ( + + {renderInline(inner, keyPrefix, extras)} + + ) : ( + {renderInline(inner, keyPrefix, extras)} + ); + } + case "url": { + const href = safeUrl(attr) ?? safeUrl(stripTags(inner)); + if (!href) return {stripTags(inner)}; + return ( + + {attr ? renderInline(inner, keyPrefix, extras) : href} + + ); + } + case "wiki": { + const title = stripTags(attr ?? inner); + if (!title) return null; + return ( + + {title} + + ); + } + case "thumb": + case "img": { + const href = safeUrl(attr) ?? safeUrl(stripTags(inner)); + if (!href) return null; + return ( + + [image] + + ); + } + default: + return null; + } +} + +function renderInline( + text: string, + keyPrefix: string, + extras: Map