8bb441870a
Resolve the pre-merge review items for the #146 NodeView content-first fix: - Export collectScrollAncestors/reflowAfterPaste and add editor-paste-handler unit tests covering ancestor selection (overlay included, non-overflowing auto excluded, X axis), the scrollHeight>clientHeight gate, scrollingElement dedup, the docEl==null branch, and the double-rAF nudge. - Extend the structural guard with CodeBlockView and merge the two it.each blocks into one document-order assertion (handles the <pre> nesting where the contentDOM is not the literal first child). - Simplify the post-paste nudge to a single scrollTo(scrollLeft, scrollTop). - Document that the post-paste reflow runs on every paste path intentionally, and cross-reference the two #146 mitigations in both fixes. - a11y: aria-hidden the decorative footnotes heading and number marker, and label the footnotes list via role="group" + aria-label so the visual reorder does not break screen-reader reading order (WCAG 1.3.2). - CHANGELOG: add a Fixed entry noting the caret fix is macOS-verified manually. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
58 lines
2.0 KiB
TypeScript
58 lines
2.0 KiB
TypeScript
import { NodeViewContent, NodeViewProps, NodeViewWrapper } from "@tiptap/react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { getFootnoteNumber } from "@docmost/editor-ext";
|
|
import classes from "./footnote.module.css";
|
|
|
|
/**
|
|
* NodeView for a single footnote definition: a decorative number marker, the
|
|
* editable content (NodeViewContent), and a "↩" back-link to its reference.
|
|
* The number is derived from the document (not stored).
|
|
*/
|
|
export default function FootnoteDefinitionView(props: NodeViewProps) {
|
|
const { node, editor } = props;
|
|
const { t } = useTranslation();
|
|
const id = node.attrs.id as string;
|
|
|
|
// Read the cached number from the numbering plugin (computed once per doc
|
|
// change) rather than recomputing the whole map on every render.
|
|
const number = getFootnoteNumber(editor.state, id) ?? "?";
|
|
|
|
const handleBack = (e: React.MouseEvent) => {
|
|
e.preventDefault();
|
|
editor.commands.scrollToReference(id);
|
|
};
|
|
|
|
return (
|
|
<NodeViewWrapper
|
|
data-footnote-def=""
|
|
data-id={id}
|
|
className={classes.definition}
|
|
style={{ ["--footnote-number" as any]: `"${number}"` }}
|
|
>
|
|
{/* #146: contentDOM MUST be the first child — a non-editable marker before
|
|
it makes click hit-testing snap the caret above. Content first; the
|
|
marker + back-link follow in DOM and are placed left/right via CSS
|
|
flex `order`. The second #146 mitigation lives in
|
|
editor-paste-handler.tsx (reflowAfterPaste). */}
|
|
<NodeViewContent className={classes.definitionContent} />
|
|
<span
|
|
className={classes.definitionMarker}
|
|
contentEditable={false}
|
|
aria-hidden="true"
|
|
>
|
|
{number}.
|
|
</span>
|
|
<span
|
|
className={classes.backLink}
|
|
contentEditable={false}
|
|
onClick={handleBack}
|
|
role="button"
|
|
aria-label={t("Back to reference")}
|
|
title={t("Back to reference")}
|
|
>
|
|
↩
|
|
</span>
|
|
</NodeViewWrapper>
|
|
);
|
|
}
|