mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-09 00:47:59 +08:00
154 lines
4.6 KiB
TypeScript
154 lines
4.6 KiB
TypeScript
/**
|
|
* RawMarkdownEditor — raw markdown editing using Monaco Editor.
|
|
* Theme follows the app's Nimbalyst theme system.
|
|
*/
|
|
|
|
import Editor, {
|
|
loader,
|
|
type OnChange,
|
|
type OnMount,
|
|
} from '@monaco-editor/react';
|
|
import { Eye } from 'lucide-react';
|
|
import type { editor as monacoEditor } from 'monaco-editor';
|
|
import type { JSX } from 'react';
|
|
import { useEffect, useRef } from 'react';
|
|
|
|
import { useTheme } from '@/components/theme-provider';
|
|
|
|
loader.config({ paths: { vs: '/vs' } });
|
|
|
|
interface Props {
|
|
content: string;
|
|
onChange?: (value: string) => void;
|
|
readOnly?: boolean;
|
|
onToggleSource?: () => void;
|
|
language?: string;
|
|
}
|
|
|
|
function defineTheme(monaco: typeof import('monaco-editor'), isDark: boolean) {
|
|
const name = isDark ? 'llmwiki-dark' : 'llmwiki-light';
|
|
monaco.editor.defineTheme(name, {
|
|
base: isDark ? 'vs-dark' : 'vs',
|
|
inherit: true,
|
|
rules: [],
|
|
colors: isDark
|
|
? {
|
|
'editor.background': '#2d2d2d',
|
|
'editor.foreground': '#ffffff',
|
|
'editor.lineHighlightBackground': '#3a3a3a',
|
|
'editor.selectionBackground': 'rgba(96,165,250,0.2)',
|
|
'editorCursor.foreground': '#60a5fa',
|
|
'editorLineNumber.foreground': '#808080',
|
|
'editorLineNumber.activeForeground': '#b3b3b3',
|
|
'editor.selectionHighlightBackground': 'rgba(96,165,250,0.1)',
|
|
}
|
|
: {
|
|
'editor.background': '#ffffff',
|
|
'editor.foreground': '#111827',
|
|
'editor.lineHighlightBackground': '#f3f4f6',
|
|
'editor.selectionBackground': 'rgba(59,130,246,0.2)',
|
|
'editorCursor.foreground': '#3b82f6',
|
|
'editorLineNumber.foreground': '#9ca3af',
|
|
'editorLineNumber.activeForeground': '#6b7280',
|
|
'editor.selectionHighlightBackground': 'rgba(59,130,246,0.1)',
|
|
},
|
|
});
|
|
return name;
|
|
}
|
|
|
|
export default function RawMarkdownEditor({
|
|
content,
|
|
onChange,
|
|
readOnly = false,
|
|
onToggleSource,
|
|
language = 'markdown',
|
|
}: Props & { showSource?: boolean }): JSX.Element {
|
|
const { theme } = useTheme();
|
|
const editorRef = useRef<monacoEditor.IStandaloneCodeEditor | null>(null);
|
|
const monacoRef = useRef<typeof import('monaco-editor') | null>(null);
|
|
const themeApplied = useRef<string>('');
|
|
|
|
const handleMount: OnMount = (editor, monaco) => {
|
|
editorRef.current = editor;
|
|
monacoRef.current = monaco;
|
|
const isDark = theme === 'dark';
|
|
const name = defineTheme(monaco, isDark);
|
|
monaco.editor.setTheme(name);
|
|
themeApplied.current = name;
|
|
};
|
|
|
|
// Switch Monaco theme when app theme changes
|
|
useEffect(() => {
|
|
if (!monacoRef.current) return;
|
|
const isDark = theme === 'dark';
|
|
const expected = isDark ? 'llmwiki-dark' : 'llmwiki-light';
|
|
if (themeApplied.current === expected) return;
|
|
const name = defineTheme(monacoRef.current, isDark);
|
|
monacoRef.current.editor.setTheme(name);
|
|
themeApplied.current = name;
|
|
}, [theme]);
|
|
|
|
const handleChange: OnChange = (value) => {
|
|
if (onChange && value !== undefined) {
|
|
onChange(value);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
flex: 1,
|
|
minHeight: 0,
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 6,
|
|
padding: '4px 12px',
|
|
borderBottom: '1px solid var(--nim-border)',
|
|
background: 'var(--nim-toolbar-bg)',
|
|
flexShrink: 0,
|
|
}}
|
|
>
|
|
<button
|
|
type="button"
|
|
onClick={onToggleSource}
|
|
className="nim-mode-btn inline-flex items-center justify-center gap-1 px-2.5 py-1 text-xs font-medium border border-accent-primary rounded bg-accent-primary-5 text-accent-primary hover:bg-accent-primary-10 transition-colors"
|
|
title="WYSIWYG"
|
|
>
|
|
<Eye className="size-3.5" />
|
|
WYSIWYG
|
|
</button>
|
|
</div>
|
|
<div style={{ flex: 1, minHeight: 0 }}>
|
|
<Editor
|
|
height="100%"
|
|
defaultLanguage={language}
|
|
value={content}
|
|
onChange={handleChange}
|
|
onMount={handleMount}
|
|
options={{
|
|
readOnly,
|
|
minimap: { enabled: false },
|
|
fontSize: 14,
|
|
fontFamily: "'SF Mono', 'JetBrains Mono', 'Fira Code', monospace",
|
|
lineNumbers: 'on',
|
|
scrollBeyondLastLine: false,
|
|
wordWrap: 'on',
|
|
tabSize: 2,
|
|
renderLineHighlight: 'line',
|
|
cursorBlinking: 'smooth',
|
|
smoothScrolling: true,
|
|
padding: { top: 16 },
|
|
automaticLayout: true,
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|