refactor: citation viewer

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-04-28 23:25:26 -07:00
parent ca9bbee06d
commit f23be16b35
14 changed files with 362 additions and 755 deletions

View file

@ -12,10 +12,7 @@ import { type EditorPreset, presetMap } from "@/components/editor/presets";
import { escapeMdxExpressions } from "@/components/editor/utils/escape-mdx";
import { Editor, EditorContainer } from "@/components/ui/editor";
/** Live editor instance returned by `usePlateEditor`. Exposed via the
* `onEditorReady` prop so callers (e.g. `EditorPanelContent`) can drive
* plugin options imperatively most notably setting
* `FindReplacePlugin`'s `search` option for citation-jump highlights. */
/** Live editor instance returned by `usePlateEditor`. */
export type PlateEditorInstance = ReturnType<typeof usePlateEditor>;
export interface PlateEditorProps {
@ -68,15 +65,6 @@ export interface PlateEditorProps {
* without modifying the core editor component.
*/
extraPlugins?: AnyPluginConfig[];
/**
* Called whenever the live editor instance (re)mounts, with `null` on
* unmount. Used by callers that need to drive plugin options imperatively
* e.g. `EditorPanelContent` setting `FindReplacePlugin`'s `search`
* option for citation-jump highlights. The callback is invoked exactly
* once per editor lifetime (the parent's `key` prop forces a fresh
* editor when needed, e.g. on edit-mode toggle).
*/
onEditorReady?: (editor: PlateEditorInstance | null) => void;
}
function PlateEditorContent({
@ -115,7 +103,6 @@ export function PlateEditor({
defaultEditing = false,
preset = "full",
extraPlugins = [],
onEditorReady,
}: PlateEditorProps) {
const lastMarkdownRef = useRef(markdown);
const lastHtmlRef = useRef(html);
@ -172,21 +159,6 @@ export function PlateEditor({
: undefined,
});
// Expose the live editor instance to imperative callers (e.g. citation
// jump highlights). We deliberately don't depend on `onEditorReady`
// itself in the cleanup closure — callers commonly pass an arrow that
// closes over a stable ref setter, but if they pass a freshly-bound
// callback per render, the `onEditorReady?.(editor)` re-fires which is
// idempotent for ref-style setters.
const onEditorReadyRef = useRef(onEditorReady);
useEffect(() => {
onEditorReadyRef.current = onEditorReady;
}, [onEditorReady]);
useEffect(() => {
onEditorReadyRef.current?.(editor);
return () => onEditorReadyRef.current?.(null);
}, [editor]);
// Update editor content when html prop changes externally
useEffect(() => {
if (html !== undefined && html !== lastHtmlRef.current) {

View file

@ -1,6 +1,5 @@
"use client";
import { FindReplacePlugin } from "@platejs/find-replace";
import type { AnyPluginConfig } from "platejs";
import { TrailingBlockPlugin } from "platejs";
@ -18,30 +17,6 @@ import { SelectionKit } from "@/components/editor/plugins/selection-kit";
import { SlashCommandKit } from "@/components/editor/plugins/slash-command-kit";
import { TableKit } from "@/components/editor/plugins/table-kit";
import { ToggleKit } from "@/components/editor/plugins/toggle-kit";
import { SearchHighlightLeaf } from "@/components/ui/search-highlight-node";
/**
* Citation-jump highlighter. Re-uses Plate's built-in `FindReplacePlugin`
* (decorate-only, no editing surface) to drive the "scroll-to-cited-text"
* UX in `EditorPanelContent`. We register it in every preset because:
* - Decorate is a no-op when `search` is empty (single getOptions() check
* per block), so cost is effectively zero for non-citation viewers.
* - Keeping it preset-agnostic means citations work whether the doc is
* opened in editable (`full`) or pure-viewer (`readonly`) modes.
*
* The parent component drives `setOption(FindReplacePlugin, 'search', ...)`
* + `editor.api.redecorate()` to trigger highlights, then queries the
* editor DOM for `.citation-highlight-leaf` to scroll the first match
* into view. (We can't use a `data-*` attribute here — Plate's
* `PlateLeaf` runs props through `useNodeAttributes`, which only forwards
* `attributes`, `className`, `ref`, `style`; arbitrary `data-*` props are
* silently dropped.) See `components/ui/search-highlight-node.tsx` for
* the leaf component and `CITATION_HIGHLIGHT_CLASS` constant.
*/
const CitationFindReplacePlugin = FindReplacePlugin.configure({
options: { search: "" },
render: { node: SearchHighlightLeaf },
});
/**
* Full preset every plugin kit enabled.
@ -63,7 +38,6 @@ export const fullPreset: AnyPluginConfig[] = [
...AutoformatKit,
...DndKit,
TrailingBlockPlugin,
CitationFindReplacePlugin,
];
/**
@ -78,7 +52,6 @@ export const minimalPreset: AnyPluginConfig[] = [
...LinkKit,
...AutoformatKit,
TrailingBlockPlugin,
CitationFindReplacePlugin,
];
/**
@ -95,7 +68,6 @@ export const readonlyPreset: AnyPluginConfig[] = [
...CalloutKit,
...ToggleKit,
...MathKit,
CitationFindReplacePlugin,
];
/** All available preset names */