@@ -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,30 @@ 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
+ const getQuill = ( ) => ( editorRef . current as any ) ?. getEditor ?.( ) ;
213
+
214
+ const tryParseDelta = ( v :unknown ) => {
215
+ if ( ! v ) return null ;
216
+ if ( typeof v === "string" ) {
217
+ try {
218
+ const d = JSON . parse ( v ) ;
219
+ return Array . isArray ( d ?. ops ) ?d :null ;
220
+ } catch { return null ; }
221
+ }
222
+ if ( typeof v === "object" && Array . isArray ( ( v as any ) . ops ) ) return v as any ;
223
+ return null ;
224
+ } ;
225
+
210
226
const isTypingRef = useRef ( 0 ) ;
211
227
212
228
const debounce = INPUT_DEFAULT_ONCHANGE_DEBOUNCE ;
213
229
214
230
const originOnChangeRef = useRef ( props . onChange ) ;
215
231
originOnChangeRef . current = props . onChange ;
216
232
217
- const onChangeRef = useRef (
218
- ( v : string ) => originOnChangeRef . current ?.( v )
233
+ const onChangeRef = useRef ( ( html : string , deltaJSON : string , text : string ) =>
234
+ originOnChangeRef . current ?.( html , deltaJSON , text )
219
235
) ;
220
236
221
237
// react-quill will not take effect after the placeholder is updated
@@ -235,7 +251,7 @@ function RichTextEditor(props: IProps) {
235
251
( editor . scroll . domNode as HTMLElement ) . tabIndex = props . tabIndex ;
236
252
}
237
253
}
238
- } , [ props . tabIndex , key ] ) ; // Also re-run when key changes due to placeholder update
254
+ } , [ props . tabIndex , key ] ) ;
239
255
240
256
const contains = ( parent :HTMLElement , descendant :HTMLElement ) => {
241
257
try {
@@ -248,19 +264,31 @@ function RichTextEditor(props: IProps) {
248
264
return parent . contains ( descendant ) ;
249
265
} ;
250
266
251
- const handleChange = ( value :string ) => {
252
- setContent ( value ) ;
253
- // props.onChange(value);
254
- onChangeRef . current ( value ) ;
255
- } ;
256
267
257
268
useEffect ( ( ) => {
258
- let finalValue = props . value ;
259
- if ( ! / ^ < \w + > .+ < \/ \w + > $ / . test ( props . value ) ) {
260
- finalValue = `<p class="">${ props . value } </p>` ;
269
+ const q = getQuill ( ) ;
270
+
271
+ if ( ! q ) {
272
+ const v = props . value ?? "" ;
273
+ const looksHtml = / < \/ ? [ a - z ] [ \s \S ] * > / i. test ( v ) ;
274
+ setContent ( looksHtml ?v :`<p class="">${ v } </p>` ) ;
275
+ return ;
261
276
}
262
- setContent ( finalValue ) ;
277
+
278
+ const asDelta = tryParseDelta ( props . value ) ;
279
+ if ( asDelta ) {
280
+ q . setContents ( asDelta , "api" ) ;
281
+ const html = q . root ?. innerHTML ?? "" ;
282
+ setContent ( html ) ;
283
+ return ;
284
+ }
285
+
286
+ const v = props . value ?? "" ;
287
+ const looksHtml = / < \/ ? [ a - z ] [ \s \S ] * > / i. test ( v ) ;
288
+ const html = looksHtml ?v :`<p class="">${ v } </p>` ;
289
+ setContent ( html ) ;
263
290
} , [ props . value ] ) ;
291
+
264
292
265
293
const handleClickWrapper = ( e :React . MouseEvent < HTMLDivElement > ) => {
266
294
// grid item prevents bubbling, quill can't listen to events on document.body, so it can't close the toolbar drop-down box
@@ -297,7 +325,13 @@ function RichTextEditor(props: IProps) {
297
325
value = { content }
298
326
placeholder = { props . placeholder }
299
327
readOnly = { props . readOnly }
300
- onChange = { handleChange }
328
+ onChange = { ( html , _delta , source , editor ) => {
329
+ setContent ( html ) ;
330
+ const quill = editorRef . current ?. getEditor ?.( ) ;
331
+ const fullDelta = quill ?. getContents ?.( ) ?? { ops :[ ] } ;
332
+ const text = quill ?. getText ?.( ) ?? "" ;
333
+ onChangeRef . current ( html , JSON . stringify ( fullDelta ) , text ) ;
334
+ } }
301
335
/>
302
336
</ Suspense >
303
337
</ Wrapper >
@@ -306,15 +340,16 @@ function RichTextEditor(props: IProps) {
306
340
307
341
const RichTextEditorCompBase = new UICompBuilder ( childrenMap , ( props ) => {
308
342
const debouncedOnChangeRef = useRef (
309
- debounce ( ( value :string ) => {
310
- props . value . onChange ( value ) ;
343
+ debounce ( ( html :string , deltaJSON :string , text :string ) => {
344
+ props . value . onChange ( html ) ;
345
+ props . delta . onChange ( deltaJSON ) ;
311
346
props . onEvent ( "change" ) ;
312
347
} , 1000 )
313
348
) ;
314
349
315
- const handleChange = ( value :string ) => {
316
- debouncedOnChangeRef . current ?.( value ) ;
317
- } ;
350
+ const handleChange = ( html : string , deltaJSON : string , text :string ) => {
351
+ debouncedOnChangeRef . current ?.( html , deltaJSON , text ) ;
352
+ } ;
318
353
319
354
return (
320
355
< RichTextEditor
@@ -379,6 +414,7 @@ class RichTextEditorCompAutoHeight extends RichTextEditorCompBase {
379
414
380
415
export const RichTextEditorComp = withExposingConfigs ( RichTextEditorCompAutoHeight , [
381
416
new NameConfig ( "value" , trans ( "export.richTextEditorValueDesc" ) ) ,
417
+ new NameConfig ( "delta" , trans ( "export.richTextEditorDeltaDesc" ) ) ,
382
418
new NameConfig ( "readOnly" , trans ( "export.richTextEditorReadOnlyDesc" ) ) ,
383
419
new NameConfig ( "hideToolbar" , trans ( "export.richTextEditorHideToolBarDesc" ) ) ,
384
420
NameConfigHidden ,