@@ -171,7 +171,8 @@ const toolbarOptions = [
171171] ;
172172
173173const childrenMap = {
174- value : stringExposingStateControl ( "value" ) ,
174+ value : stringExposingStateControl ( "value" ) ,
175+ delta : stringExposingStateControl ( "delta" ) ,
175176 hideToolbar : BoolControl ,
176177 readOnly : BoolControl ,
177178 autoHeight : withDefault ( AutoHeightControl , "fixed" ) ,
@@ -194,7 +195,7 @@ interface IProps {
194195 hideToolbar : boolean ;
195196 readOnly : boolean ;
196197 autoHeight : boolean ;
197- onChange : ( value : string ) => void ;
198+ onChange : ( html : string , deltaJSON : string , text : string ) => void ;
198199 $style : RichTextEditorStyleType ;
199200 contentScrollBar : boolean ;
200201 tabIndex ?: number ;
@@ -207,15 +208,37 @@ function RichTextEditor(props: IProps) {
207208 const [ content , setContent ] = useState ( "" ) ;
208209 const wrapperRef = useRef < HTMLDivElement > ( null ) ;
209210 const editorRef = useRef < ReactQuill > ( null ) ;
211+
212+ // know exactly when the editor mounts
213+ const [ editorReady , setEditorReady ] = useState ( false ) ;
214+ const setEditorRef = ( node : ReactQuill | null ) => {
215+ ( editorRef as any ) . current = node as any ;
216+ setEditorReady ( ! ! node ) ;
217+ } ;
218+
219+ const getQuill = ( ) => ( editorRef . current as any ) ?. getEditor ?.( ) ;
220+
221+ const tryParseDelta = ( v : unknown ) => {
222+ if ( ! v ) return null ;
223+ if ( typeof v === "string" ) {
224+ try {
225+ const d = JSON . parse ( v ) ;
226+ return Array . isArray ( d ?. ops ) ? d : null ;
227+ } catch { return null ; }
228+ }
229+ if ( typeof v === "object" && Array . isArray ( ( v as any ) . ops ) ) return v as any ;
230+ return null ;
231+ } ;
232+
210233 const isTypingRef = useRef ( 0 ) ;
211234
212235 const debounce = INPUT_DEFAULT_ONCHANGE_DEBOUNCE ;
213236
214237 const originOnChangeRef = useRef ( props . onChange ) ;
215238 originOnChangeRef . current = props . onChange ;
216239
217- const onChangeRef = useRef (
218- ( v : string ) => originOnChangeRef . current ?.( v )
240+ const onChangeRef = useRef ( ( html : string , deltaJSON : string , text : string ) =>
241+ originOnChangeRef . current ?.( html , deltaJSON , text )
219242 ) ;
220243
221244 // react-quill will not take effect after the placeholder is updated
@@ -235,7 +258,7 @@ function RichTextEditor(props: IProps) {
235258 ( editor . scroll . domNode as HTMLElement ) . tabIndex = props . tabIndex ;
236259 }
237260 }
238- } , [ props . tabIndex , key ] ) ; // Also re-run when key changes due to placeholder update
261+ } , [ props . tabIndex , key ] ) ;
239262
240263 const contains = ( parent : HTMLElement , descendant : HTMLElement ) => {
241264 try {
@@ -248,19 +271,26 @@ function RichTextEditor(props: IProps) {
248271 return parent . contains ( descendant ) ;
249272 } ;
250273
251- const handleChange = ( value : string ) => {
252- setContent ( value ) ;
253- // props.onChange(value);
254- onChangeRef . current ( value ) ;
255- } ;
256274
257275 useEffect ( ( ) => {
258- let finalValue = props . value ;
259- if ( ! / ^ < \w + > . + < \/ \w + > $ / . test ( props . value ) ) {
260- finalValue = `<p class=""> ${ props . value } </p>` ;
276+ const q = getQuill ( ) ;
277+ if ( ! q ) {
278+ return ;
261279 }
262- setContent ( finalValue ) ;
263- } , [ props . value ] ) ;
280+
281+ const asDelta = tryParseDelta ( props . value ) ;
282+ if ( asDelta ) {
283+ q . setContents ( asDelta , "api" ) ;
284+ const html = q . root ?. innerHTML ?? "" ;
285+ setContent ( html ) ;
286+ return ;
287+ }
288+ const v = props . value ?? "" ;
289+ const looksHtml = / < \/ ? [ a - z ] [ \s \S ] * > / i. test ( v ) ;
290+ const html = looksHtml ? v : `<p class="">${ v } </p>` ;
291+ setContent ( html ) ;
292+ } , [ props . value , editorReady ] ) ;
293+
264294
265295 const handleClickWrapper = ( e : React . MouseEvent < HTMLDivElement > ) => {
266296 // grid item prevents bubbling, quill can't listen to events on document.body, so it can't close the toolbar drop-down box
@@ -288,7 +318,7 @@ function RichTextEditor(props: IProps) {
288318 < Suspense fallback = { < Skeleton /> } >
289319 < ReactQuillEditor
290320 key = { key }
291- ref = { editorRef }
321+ ref = { setEditorRef }
292322 bounds = { `#${ id } ` }
293323 modules = { {
294324 toolbar : JSON . parse ( props . toolbar ) ,
@@ -297,23 +327,39 @@ function RichTextEditor(props: IProps) {
297327 value = { content }
298328 placeholder = { props . placeholder }
299329 readOnly = { props . readOnly }
300- onChange = { handleChange }
330+ onChange = { ( html , _delta , source , editor ) => {
331+ setContent ( html ) ;
332+ const quill = editorRef . current ?. getEditor ?.( ) ;
333+ const fullDelta = quill ?. getContents ?.( ) ?? { ops : [ ] } ;
334+ const text = quill ?. getText ?.( ) ?? "" ;
335+ onChangeRef . current ( html , JSON . stringify ( fullDelta ) , text ) ;
336+ } }
301337 />
302338 </ Suspense >
303339 </ Wrapper >
304340 ) ;
305341}
306342
307343const RichTextEditorCompBase = new UICompBuilder ( childrenMap , ( props ) => {
344+ const propsRef = useRef ( props ) ;
345+ propsRef . current = props ;
346+
308347 const debouncedOnChangeRef = useRef (
309- debounce ( ( value : string ) => {
310- props . value . onChange ( value ) ;
311- props . onEvent ( "change" ) ;
312- } , 1000 )
348+ debounce ( ( html : string , deltaJSON : string , text : string ) => {
349+ propsRef . current . value . onChange ( html ) ;
350+ propsRef . current . delta . onChange ( deltaJSON ) ;
351+ propsRef . current . onEvent ( "change" ) ;
352+ } , 500 )
313353 ) ;
314354
315- const handleChange = ( value : string ) => {
316- debouncedOnChangeRef . current ?.( value ) ;
355+ useEffect ( ( ) => {
356+ return ( ) => {
357+ debouncedOnChangeRef . current ?. cancel ( ) ;
358+ } ;
359+ } , [ ] ) ;
360+
361+ const handleChange = ( html : string , deltaJSON : string , text : string ) => {
362+ debouncedOnChangeRef . current ?.( html , deltaJSON , text ) ;
317363 } ;
318364
319365 return (
@@ -379,6 +425,7 @@ class RichTextEditorCompAutoHeight extends RichTextEditorCompBase {
379425
380426export const RichTextEditorComp = withExposingConfigs ( RichTextEditorCompAutoHeight , [
381427 new NameConfig ( "value" , trans ( "export.richTextEditorValueDesc" ) ) ,
428+ new NameConfig ( "delta" , trans ( "export.richTextEditorDeltaDesc" ) ) ,
382429 new NameConfig ( "readOnly" , trans ( "export.richTextEditorReadOnlyDesc" ) ) ,
383430 new NameConfig ( "hideToolbar" , trans ( "export.richTextEditorHideToolBarDesc" ) ) ,
384431 NameConfigHidden ,
0 commit comments