diff --git a/surfsense_web/components/editor/plate-editor.tsx b/surfsense_web/components/editor/plate-editor.tsx index 3313a27f0..510d95110 100644 --- a/surfsense_web/components/editor/plate-editor.tsx +++ b/surfsense_web/components/editor/plate-editor.tsx @@ -1,7 +1,7 @@ 'use client'; import { useEffect, useRef } from 'react'; -import { MarkdownPlugin } from '@platejs/markdown'; +import { MarkdownPlugin, remarkMdx } from '@platejs/markdown'; import { Plate, usePlateEditor } from 'platejs/react'; import remarkGfm from 'remark-gfm'; 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 { ToggleKit } from '@/components/editor/plugins/toggle-kit'; import { Editor, EditorContainer } from '@/components/ui/editor'; +import { escapeMdxExpressions } from '@/components/editor/utils/escape-mdx'; interface PlateEditorProps { /** Markdown string to load as initial content */ @@ -68,13 +69,16 @@ export function PlateEditor({ ...DndKit, MarkdownPlugin.configure({ options: { - remarkPlugins: [remarkGfm, remarkMath], + remarkPlugins: [remarkGfm, remarkMath, remarkMdx], }, }), ], // Use markdown deserialization for initial value if provided value: markdown - ? (editor) => editor.getApi(MarkdownPlugin).markdown.deserialize(markdown) + ? (editor) => + editor + .getApi(MarkdownPlugin) + .markdown.deserialize(escapeMdxExpressions(markdown)) : undefined, }); @@ -83,7 +87,9 @@ export function PlateEditor({ useEffect(() => { if (markdown !== undefined && markdown !== lastMarkdownRef.current) { 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.setValue(newValue); } diff --git a/surfsense_web/components/editor/utils/escape-mdx.ts b/surfsense_web/components/editor/utils/escape-mdx.ts new file mode 100644 index 000000000..7835ef7ac --- /dev/null +++ b/surfsense_web/components/editor/utils/escape-mdx.ts @@ -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 +// , , , 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(/(? + {props.children} ); diff --git a/surfsense_web/components/ui/link-node.tsx b/surfsense_web/components/ui/link-node.tsx index 0fddec3b3..82e916ed0 100644 --- a/surfsense_web/components/ui/link-node.tsx +++ b/surfsense_web/components/ui/link-node.tsx @@ -16,7 +16,7 @@ export function LinkElement(props: PlateElementProps) { {...props} as="a" 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={{ ...props.attributes, diff --git a/surfsense_web/components/ui/mark-toolbar-button.tsx b/surfsense_web/components/ui/mark-toolbar-button.tsx index 1f9405756..4534ab8b9 100644 --- a/surfsense_web/components/ui/mark-toolbar-button.tsx +++ b/surfsense_web/components/ui/mark-toolbar-button.tsx @@ -17,5 +17,13 @@ export function MarkToolbarButton({ const state = useMarkToolbarButtonState({ clear, nodeType }); const { props: buttonProps } = useMarkToolbarButton(state); - return ; + return ( + { + e.preventDefault(); + }} + /> + ); }