Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion internal/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,8 @@ export function App() {

const handleHeadingClick = useCallback((id: string) => {
const el = document.getElementById(id);
el?.scrollIntoView({ behavior: "smooth", block: "start" });
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
el?.scrollIntoView({ behavior: reduced ? "auto" : "smooth", block: "start" });
}, []);

const handleZoom = useCallback((content: ZoomContent) => {
Expand Down
46 changes: 45 additions & 1 deletion internal/frontend/src/components/MarkdownViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,31 @@ import type { TocHeading } from "./TocPanel";
import type { Components } from "react-markdown";
import "github-markdown-css/github-markdown.css";

// Strip the `user-content-` prefix that remark-gfm bakes into footnote IDs,
// so rehype-sanitize can re-add it exactly once (avoiding double-prefixed IDs).
function rehypeStripClobberPrefix() {
const FOOTNOTE_ID_PATTERN = /^user-content-(fn-|fnref-|footnote-label$)/;
const PREFIX = "user-content-";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function walk(node: any) {
if (node.properties) {
const props = node.properties;
if (typeof props.id === "string" && FOOTNOTE_ID_PATTERN.test(props.id)) {
props.id = props.id.slice(PREFIX.length);
}
}
if (node.children) {
for (const child of node.children) {
if (child.type === "element") walk(child);
}
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (tree: any) => {
walk(tree);
};
}
Comment thread
k1LoW marked this conversation as resolved.

// Extend default GitHub-compatible schema to allow style/align attributes used in raw HTML
const sanitizeSchema = {
...defaultSchema,
Expand Down Expand Up @@ -603,7 +628,25 @@ export function MarkdownViewer({
);
case "hash":
return (
<a href={href} {...props}>
<a
href={href}
onClick={(e) => {
if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
const id = href?.slice(1);
if (!id) return;
const target = document.getElementById(id);
if (target) {
Comment thread
k1LoW marked this conversation as resolved.
e.preventDefault();
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
target.scrollIntoView({
behavior: reduced ? "auto" : "smooth",
block: "start",
});
history.pushState(null, "", href);
}
Comment thread
k1LoW marked this conversation as resolved.
}}
{...props}
>
{children}
</a>
);
Expand Down Expand Up @@ -655,6 +698,7 @@ export function MarkdownViewer({
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[
rehypeRaw,
rehypeStripClobberPrefix,
[rehypeSanitize, sanitizeSchema],
Comment thread
k1LoW marked this conversation as resolved.
rehypeGithubAlerts,
rehypeSlug,
Expand Down
4 changes: 3 additions & 1 deletion testdata/long-document.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Long Document

This file tests scrolling and rendering performance with a longer document.
This file tests scrolling and rendering performance with a longer document[^1].

## Section 1: Introduction

Expand Down Expand Up @@ -134,3 +134,5 @@ export async function fetchFileContent(
---

*End of document*

[^1]: This is a footnote near the top of the document, referenced from the introduction. It demonstrates smooth scroll navigation between footnote references and their definitions.
Loading