@@ -171,7 +171,8 @@ const toolbarOptions = [
171
171
] ;
172
172
173
173
const childrenMap = {
174
- value : stringExposingStateControl ( "value" ) ,
174
+ value : stringExposingStateControl ( "value" ) ,
175
+ delta : stringExposingStateControl ( "delta" ) ,
175
176
hideToolbar : BoolControl ,
176
177
readOnly : BoolControl ,
177
178
autoHeight : withDefault ( AutoHeightControl , "fixed" ) ,
@@ -194,7 +195,7 @@ interface IProps {
194
195
hideToolbar : boolean ;
195
196
readOnly : boolean ;
196
197
autoHeight : boolean ;
197
- onChange : ( value : string ) => void ;
198
+ onChange : ( html : string , deltaJSON : string , text : string ) => void ;
198
199
$style : RichTextEditorStyleType ;
199
200
contentScrollBar : boolean ;
200
201
tabIndex ?: number ;
@@ -207,15 +208,37 @@ function RichTextEditor(props: IProps) {
207
208
const [ content , setContent ] = useState ( "" ) ;
208
209
const wrapperRef = useRef < HTMLDivElement > ( null ) ;
209
210
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
+
210
233
const isTypingRef = useRef ( 0 ) ;
211
234
212
235
const debounce = INPUT_DEFAULT_ONCHANGE_DEBOUNCE ;
213
236
214
237
const originOnChangeRef = useRef ( props . onChange ) ;
215
238
originOnChangeRef . current = props . onChange ;
216
239
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 )
219
242
) ;
220
243
221
244
// react-quill will not take effect after the placeholder is updated
@@ -235,7 +258,7 @@ function RichTextEditor(props: IProps) {
235
258
( editor . scroll . domNode as HTMLElement ) . tabIndex = props . tabIndex ;
236
259
}
237
260
}
238
- } , [ props . tabIndex , key ] ) ; // Also re-run when key changes due to placeholder update
261
+ } , [ props . tabIndex , key ] ) ;
239
262
240
263
const contains = ( parent : HTMLElement , descendant : HTMLElement ) => {
241
264
try {
@@ -248,19 +271,26 @@ function RichTextEditor(props: IProps) {
248
271
return parent . contains ( descendant ) ;
249
272
} ;
250
273
251
- const handleChange = ( value : string ) => {
252
- setContent ( value ) ;
253
- // props.onChange(value);
254
- onChangeRef . current ( value ) ;
255
- } ;
256
274
257
275
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 ;
261
279
}
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
+
264
294
265
295
const handleClickWrapper = ( e : React . MouseEvent < HTMLDivElement > ) => {
266
296
// 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) {
288
318
< Suspense fallback = { < Skeleton /> } >
289
319
< ReactQuillEditor
290
320
key = { key }
291
- ref = { editorRef }
321
+ ref = { setEditorRef }
292
322
bounds = { `#${ id } ` }
293
323
modules = { {
294
324
toolbar : JSON . parse ( props . toolbar ) ,
@@ -297,23 +327,39 @@ function RichTextEditor(props: IProps) {
297
327
value = { content }
298
328
placeholder = { props . placeholder }
299
329
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
+ } }
301
337
/>
302
338
</ Suspense >
303
339
</ Wrapper >
304
340
) ;
305
341
}
306
342
307
343
const RichTextEditorCompBase = new UICompBuilder ( childrenMap , ( props ) => {
344
+ const propsRef = useRef ( props ) ;
345
+ propsRef . current = props ;
346
+
308
347
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 )
313
353
) ;
314
354
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 ) ;
317
363
} ;
318
364
319
365
return (
@@ -379,6 +425,7 @@ class RichTextEditorCompAutoHeight extends RichTextEditorCompBase {
379
425
380
426
export const RichTextEditorComp = withExposingConfigs ( RichTextEditorCompAutoHeight , [
381
427
new NameConfig ( "value" , trans ( "export.richTextEditorValueDesc" ) ) ,
428
+ new NameConfig ( "delta" , trans ( "export.richTextEditorDeltaDesc" ) ) ,
382
429
new NameConfig ( "readOnly" , trans ( "export.richTextEditorReadOnlyDesc" ) ) ,
383
430
new NameConfig ( "hideToolbar" , trans ( "export.richTextEditorHideToolBarDesc" ) ) ,
384
431
NameConfigHidden ,
0 commit comments