33import { useEffect , useRef , useState } from 'react'
44import type { JSONContent } from '@tiptap/core'
55import { EditorContent , useEditor } from '@tiptap/react'
6- import { chipFieldSurfaceClass } from '@/components/emcn'
6+ import { ChipTextarea , chipFieldSurfaceClass } from '@/components/emcn'
77import { cn } from '@/lib/core/utils/cn'
88import { createMarkdownEditorExtensions } from './extensions'
99import {
@@ -16,6 +16,7 @@ import { useEditorMentions } from './mention'
1616import { EditorBubbleMenu } from './menus/bubble-menu'
1717import { LinkHoverCard } from './menus/link-hover-card'
1818import { normalizeMarkdownContent } from './normalize-content'
19+ import { isRoundTripSafe } from './round-trip-safety'
1920import '@/components/emcn/components/code/code.css'
2021import './rich-markdown-editor.css'
2122
@@ -47,11 +48,11 @@ interface RichMarkdownFieldProps {
4748}
4849
4950/**
50- * A controlled, string-valued WYSIWYG markdown editor for modal fields — the file-less sibling of
51- * {@link RichMarkdownEditor}. It reuses the same TipTap extensions, parser, and menus but owns no file
52- * loading, autosave, or image upload. Drop it inside a `ChipModalField type='custom'` .
51+ * The WYSIWYG editor for round-trip-safe content (chosen by { @link RichMarkdownField}). The file-less
52+ * sibling of {@link RichMarkdownEditor}'s loaded editor: same TipTap extensions, parser, and menus but
53+ * no file loading, autosave, or image upload.
5354 */
54- export function RichMarkdownField ( {
55+ function LoadedRichMarkdownField ( {
5556 value,
5657 onChange,
5758 placeholder = "Write something, or press '/' for commands…" ,
@@ -188,3 +189,40 @@ export function RichMarkdownField({
188189 </ div >
189190 )
190191}
192+
193+ /**
194+ * Raw-text fallback for content the rich editor can't round-trip losslessly — editing the markdown
195+ * source directly so an edit can't silently drop footnotes, raw HTML, or comments.
196+ */
197+ function RawMarkdownField ( {
198+ value,
199+ onChange,
200+ placeholder,
201+ disabled = false ,
202+ isStreaming = false ,
203+ minHeight = 140 ,
204+ maxHeight = 360 ,
205+ error = false ,
206+ } : RichMarkdownFieldProps ) {
207+ return (
208+ < ChipTextarea
209+ value = { value }
210+ onChange = { ( event ) => onChange ( event . target . value ) }
211+ placeholder = { placeholder }
212+ error = { error }
213+ readOnly = { disabled || isStreaming }
214+ style = { { minHeight, maxHeight } }
215+ />
216+ )
217+ }
218+
219+ /**
220+ * A controlled, string-valued markdown editor for modal fields. Drop it inside a `ChipModalField
221+ * type='custom'`. Mirrors the file editor's safety gate (decided once from the initial value):
222+ * round-trip-safe content opens in the WYSIWYG editor, while lossy markdown (raw HTML, footnotes,
223+ * comments) falls back to raw-text editing so an edit can't silently drop those constructs.
224+ */
225+ export function RichMarkdownField ( props : RichMarkdownFieldProps ) {
226+ const [ isSafe ] = useState ( ( ) => isRoundTripSafe ( props . value ) )
227+ return isSafe ? < LoadedRichMarkdownField { ...props } /> : < RawMarkdownField { ...props } />
228+ }
0 commit comments