mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-06 06:12:40 +02:00
refactor(plate-editor): replace markdown deserialization with safeDeserializeMarkdown utility
This commit is contained in:
parent
5e87a7a251
commit
0654662d29
2 changed files with 79 additions and 13 deletions
|
|
@ -11,6 +11,7 @@ import { EditorSaveContext } from "@/components/editor/editor-save-context";
|
|||
import { CitationKit, injectCitationNodes } from "@/components/editor/plugins/citation-kit";
|
||||
import { type EditorPreset, presetMap } from "@/components/editor/presets";
|
||||
import { escapeMdxExpressions } from "@/components/editor/utils/escape-mdx";
|
||||
import { safeDeserializeMarkdown } from "@/components/editor/utils/safe-deserialize";
|
||||
import { Editor, EditorContainer } from "@/components/ui/editor";
|
||||
import { preprocessCitationMarkdown } from "@/lib/citations/citation-parser";
|
||||
|
||||
|
|
@ -169,15 +170,17 @@ export function PlateEditor({
|
|||
: markdown
|
||||
? (editor) => {
|
||||
if (!enableCitations) {
|
||||
return editor
|
||||
.getApi(MarkdownPlugin)
|
||||
.markdown.deserialize(escapeMdxExpressions(markdown));
|
||||
return safeDeserializeMarkdown(
|
||||
editor,
|
||||
escapeMdxExpressions(markdown)
|
||||
) as Value;
|
||||
}
|
||||
const { content: rewritten, urlMap } = preprocessCitationMarkdown(markdown);
|
||||
const value = editor
|
||||
.getApi(MarkdownPlugin)
|
||||
.markdown.deserialize(escapeMdxExpressions(rewritten));
|
||||
return injectCitationNodes(value as Descendant[], urlMap) as Value;
|
||||
const value = safeDeserializeMarkdown(
|
||||
editor,
|
||||
escapeMdxExpressions(rewritten)
|
||||
);
|
||||
return injectCitationNodes(value, urlMap) as Value;
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
|
@ -200,14 +203,13 @@ export function PlateEditor({
|
|||
let newValue: Descendant[];
|
||||
if (enableCitations) {
|
||||
const { content: rewritten, urlMap } = preprocessCitationMarkdown(markdown);
|
||||
const deserialized = editor
|
||||
.getApi(MarkdownPlugin)
|
||||
.markdown.deserialize(escapeMdxExpressions(rewritten)) as Descendant[];
|
||||
const deserialized = safeDeserializeMarkdown(
|
||||
editor,
|
||||
escapeMdxExpressions(rewritten)
|
||||
);
|
||||
newValue = injectCitationNodes(deserialized, urlMap);
|
||||
} else {
|
||||
newValue = editor
|
||||
.getApi(MarkdownPlugin)
|
||||
.markdown.deserialize(escapeMdxExpressions(markdown)) as Descendant[];
|
||||
newValue = safeDeserializeMarkdown(editor, escapeMdxExpressions(markdown));
|
||||
}
|
||||
editor.tf.reset();
|
||||
editor.tf.setValue(newValue as Value);
|
||||
|
|
|
|||
64
surfsense_web/components/editor/utils/safe-deserialize.ts
Normal file
64
surfsense_web/components/editor/utils/safe-deserialize.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// Safe markdown deserialization for the Plate editor
|
||||
// ---------------------------------------------------------------------------
|
||||
// `remark-mdx` treats any HTML-like tag as JSX, so unbalanced inline HTML
|
||||
// (very common in GitHub READMEs, web-scraped pages, PDF conversions) makes
|
||||
// it throw "Expected a closing tag for `<a>`" and crash the editor.
|
||||
//
|
||||
// Per the MDX maintainers' guidance (mdx-js/mdx, ipikuka/next-mdx-remote-client
|
||||
// #14), MDX is the wrong format for untrusted markdown and the recommended
|
||||
// fix is to fall back to plain markdown parsing. `MarkdownPlugin.deserialize`
|
||||
// accepts a per-call `remarkPlugins` override, so we can:
|
||||
//
|
||||
// 1. Try with `remarkMdx` (rich MDX features, e.g. JSX-style components).
|
||||
// 2. On failure, retry without `remarkMdx` (lenient HTML, like GitHub).
|
||||
// 3. As a last resort, render the raw source in a paragraph so the user
|
||||
// never sees a crashed editor.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
import { MarkdownPlugin, remarkMdx } from "@platejs/markdown";
|
||||
import type { Descendant } from "platejs";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkMath from "remark-math";
|
||||
import type { PlateEditorInstance } from "@/components/editor/plate-editor";
|
||||
|
||||
const STRICT_PLUGINS = [remarkGfm, remarkMath, remarkMdx];
|
||||
const LENIENT_PLUGINS = [remarkGfm, remarkMath];
|
||||
|
||||
function plainTextFallback(markdown: string): Descendant[] {
|
||||
return [
|
||||
{
|
||||
type: "p",
|
||||
children: [{ text: markdown }],
|
||||
} as unknown as Descendant,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize markdown into a Plate value, gracefully degrading when the
|
||||
* MDX-strict parser rejects raw HTML. Always returns a renderable value;
|
||||
* never throws.
|
||||
*/
|
||||
export function safeDeserializeMarkdown(
|
||||
editor: PlateEditorInstance,
|
||||
markdown: string
|
||||
): Descendant[] {
|
||||
const api = editor.getApi(MarkdownPlugin).markdown;
|
||||
|
||||
try {
|
||||
return api.deserialize(markdown, { remarkPlugins: STRICT_PLUGINS }) as Descendant[];
|
||||
} catch (mdxError) {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
console.warn(
|
||||
"[plate-editor] MDX parse failed, retrying without remark-mdx:",
|
||||
mdxError
|
||||
);
|
||||
}
|
||||
try {
|
||||
return api.deserialize(markdown, { remarkPlugins: LENIENT_PLUGINS }) as Descendant[];
|
||||
} catch (fallbackError) {
|
||||
console.error("[plate-editor] markdown deserialize failed:", fallbackError);
|
||||
return plainTextFallback(markdown);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue