mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-11 16:52:38 +02:00
feat: enhance PlateEditor with MDX expression escaping and add remarkMdx support for improved markdown parsing
This commit is contained in:
parent
2d74d7bc4b
commit
648b00da64
5 changed files with 47 additions and 7 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { MarkdownPlugin } from '@platejs/markdown';
|
import { MarkdownPlugin, remarkMdx } from '@platejs/markdown';
|
||||||
import { Plate, usePlateEditor } from 'platejs/react';
|
import { Plate, usePlateEditor } from 'platejs/react';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import remarkMath from 'remark-math';
|
import remarkMath from 'remark-math';
|
||||||
|
|
@ -21,6 +21,7 @@ import { SlashCommandKit } from '@/components/editor/plugins/slash-command-kit';
|
||||||
import { TableKit } from '@/components/editor/plugins/table-kit';
|
import { TableKit } from '@/components/editor/plugins/table-kit';
|
||||||
import { ToggleKit } from '@/components/editor/plugins/toggle-kit';
|
import { ToggleKit } from '@/components/editor/plugins/toggle-kit';
|
||||||
import { Editor, EditorContainer } from '@/components/ui/editor';
|
import { Editor, EditorContainer } from '@/components/ui/editor';
|
||||||
|
import { escapeMdxExpressions } from '@/components/editor/utils/escape-mdx';
|
||||||
|
|
||||||
interface PlateEditorProps {
|
interface PlateEditorProps {
|
||||||
/** Markdown string to load as initial content */
|
/** Markdown string to load as initial content */
|
||||||
|
|
@ -68,13 +69,16 @@ export function PlateEditor({
|
||||||
...DndKit,
|
...DndKit,
|
||||||
MarkdownPlugin.configure({
|
MarkdownPlugin.configure({
|
||||||
options: {
|
options: {
|
||||||
remarkPlugins: [remarkGfm, remarkMath],
|
remarkPlugins: [remarkGfm, remarkMath, remarkMdx],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
// Use markdown deserialization for initial value if provided
|
// Use markdown deserialization for initial value if provided
|
||||||
value: markdown
|
value: markdown
|
||||||
? (editor) => editor.getApi(MarkdownPlugin).markdown.deserialize(markdown)
|
? (editor) =>
|
||||||
|
editor
|
||||||
|
.getApi(MarkdownPlugin)
|
||||||
|
.markdown.deserialize(escapeMdxExpressions(markdown))
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -83,7 +87,9 @@ export function PlateEditor({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (markdown !== undefined && markdown !== lastMarkdownRef.current) {
|
if (markdown !== undefined && markdown !== lastMarkdownRef.current) {
|
||||||
lastMarkdownRef.current = markdown;
|
lastMarkdownRef.current = markdown;
|
||||||
const newValue = editor.getApi(MarkdownPlugin).markdown.deserialize(markdown);
|
const newValue = editor
|
||||||
|
.getApi(MarkdownPlugin)
|
||||||
|
.markdown.deserialize(escapeMdxExpressions(markdown));
|
||||||
editor.tf.reset();
|
editor.tf.reset();
|
||||||
editor.tf.setValue(newValue);
|
editor.tf.setValue(newValue);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
surfsense_web/components/editor/utils/escape-mdx.ts
Normal file
26
surfsense_web/components/editor/utils/escape-mdx.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// MDX curly-brace escaping helper
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// remarkMdx treats { } as JSX expression delimiters. Arbitrary markdown
|
||||||
|
// (e.g. AI-generated reports) can contain curly braces that are NOT valid JS
|
||||||
|
// expressions, which makes acorn throw "Could not parse expression".
|
||||||
|
// We escape unescaped { and } *outside* of fenced code blocks and inline code
|
||||||
|
// so remarkMdx treats them as literal characters while still parsing
|
||||||
|
// <mark>, <u>, <kbd>, etc. tags correctly.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const FENCED_OR_INLINE_CODE = /(```[\s\S]*?```|`[^`\n]+`)/g;
|
||||||
|
|
||||||
|
export function escapeMdxExpressions(md: string): string {
|
||||||
|
const parts = md.split(FENCED_OR_INLINE_CODE);
|
||||||
|
|
||||||
|
return parts
|
||||||
|
.map((part, i) => {
|
||||||
|
// Odd indices are code blocks / inline code – leave untouched
|
||||||
|
if (i % 2 === 1) return part;
|
||||||
|
// Escape { and } that are NOT already escaped (no preceding \)
|
||||||
|
return part.replace(/(?<!\\)\{/g, '\\{').replace(/(?<!\\)\}/g, '\\}');
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { PlateLeaf } from 'platejs/react';
|
||||||
|
|
||||||
export function HighlightLeaf(props: PlateLeafProps) {
|
export function HighlightLeaf(props: PlateLeafProps) {
|
||||||
return (
|
return (
|
||||||
<PlateLeaf {...props} as="mark" className="bg-highlight/30 text-inherit">
|
<PlateLeaf {...props} as="mark" className="bg-yellow-200 dark:bg-yellow-800 text-inherit">
|
||||||
{props.children}
|
{props.children}
|
||||||
</PlateLeaf>
|
</PlateLeaf>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export function LinkElement(props: PlateElementProps<TLinkElement>) {
|
||||||
{...props}
|
{...props}
|
||||||
as="a"
|
as="a"
|
||||||
className={cn(
|
className={cn(
|
||||||
'font-medium text-primary underline decoration-primary underline-offset-4'
|
'font-medium text-blue-600 underline decoration-blue-600 underline-offset-4 hover:text-blue-800 dark:text-blue-400 dark:decoration-blue-400 dark:hover:text-blue-300'
|
||||||
)}
|
)}
|
||||||
attributes={{
|
attributes={{
|
||||||
...props.attributes,
|
...props.attributes,
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,13 @@ export function MarkToolbarButton({
|
||||||
const state = useMarkToolbarButtonState({ clear, nodeType });
|
const state = useMarkToolbarButtonState({ clear, nodeType });
|
||||||
const { props: buttonProps } = useMarkToolbarButton(state);
|
const { props: buttonProps } = useMarkToolbarButton(state);
|
||||||
|
|
||||||
return <ToolbarButton {...props} {...buttonProps} />;
|
return (
|
||||||
|
<ToolbarButton
|
||||||
|
{...props}
|
||||||
|
{...buttonProps}
|
||||||
|
onMouseDown={(e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue