@@ -351,13 +351,13 @@ class DocBlockWidget extends WidgetType {
351351 // Handle both cases.
352352 const [ contents_div , is_tinymce ] = get_contents ( dom ) ;
353353 if ( is_tinymce ) {
354- tinymce_singleton ! . setContent ( this . contents ) ;
355354 ignore_next_dirty = true ;
355+ tinymce_singleton ! . setContent ( this . contents ) ;
356356 tinymce_singleton ! . save ( ) ;
357357 } else {
358358 contents_div . innerHTML = this . contents ;
359+ mathJaxTypeset ( contents_div ) ;
359360 }
360- mathJaxTypeset ( contents_div ) ;
361361
362362 // Indicate the update was successful.
363363 return true ;
@@ -380,12 +380,8 @@ class DocBlockWidget extends WidgetType {
380380 destroy ( dom : HTMLElement ) : void {
381381 // If this is the TinyMCE editor, save it.
382382 const [ contents_div , is_tinymce ] = get_contents ( dom ) ;
383- // Revert the typeset math to its original form.
384- window . MathJax . startup . document
385- . getMathItemsWithin ( contents_div )
386- . forEach ( ( item : any ) => {
387- item . removeFromDocument ( true ) ;
388- } ) ;
383+ // Forget about any typeset math in this node.
384+ window . MathJax . typesetClear ( [ contents_div ] ) ;
389385 if ( is_tinymce ) {
390386 const codechat_body = document . getElementById ( "CodeChat-body" ) ! ;
391387 const tinymce_div = document . getElementById ( "TinyMCE-inst" ) ! ;
@@ -396,12 +392,28 @@ class DocBlockWidget extends WidgetType {
396392
397393// Typeset the provided node; taken from the [MathJax
398394// docs](https://docs.mathjax.org/en/latest/web/typeset.html#handling-asynchronous-typesetting).
399- export const mathJaxTypeset = ( node : HTMLElement ) => {
400- window . MathJax . typesetPromise ( [ node ] ) . catch ( ( err : any ) =>
401- console . log ( "Typeset failed: " + err . message ) ,
402- ) ;
395+ export const mathJaxTypeset = async (
396+ // The node to typeset.
397+ node : HTMLElement ,
398+ // An optional function to run when the typeset finishes.
399+ afterTypesetFunc : ( ) => void = ( ) => { } ) => {
400+ try {
401+ await window . MathJax . typesetPromise ( [ node ] ) ;
402+ afterTypesetFunc ( ) ;
403+ } catch ( err : any ) {
404+ console . log ( "Typeset failed: " + err . message ) ;
405+ }
403406} ;
404407
408+ // Transform a typeset node back to the original (untypeset) text.
409+ export const mathJaxUnTypeset = ( node : HTMLElement ) => {
410+ window . MathJax . startup . document
411+ . getMathItemsWithin ( node )
412+ . forEach ( ( item : any ) => {
413+ item . removeFromDocument ( true ) ;
414+ } ) ;
415+ }
416+
405417// Given a doc block div element, return the contents div and if TinyMCE is
406418// attached to that div.
407419const get_contents = ( element : HTMLElement ) : [ HTMLDivElement , boolean ] => {
@@ -412,10 +424,12 @@ const get_contents = (element: HTMLElement): [HTMLDivElement, boolean] => {
412424
413425// Determine if the element which generated the provided event was in a doc
414426// block or not. If not, return false; if so, return the doc block div.
415- const event_is_in_doc_block = ( event : Event ) : boolean | HTMLDivElement => {
416- const target = event . target as HTMLElement ;
427+ const element_is_in_doc_block = ( target : EventTarget | null ) : boolean | HTMLDivElement => {
428+ if ( target === null ) {
429+ return false ;
430+ }
417431 // Look for either a CodeMirror ancestor or a CodeChat doc block ancestor.
418- const ancestor = target . closest ( ".cm-line, .CodeChat-doc" ) ;
432+ const ancestor = ( target as HTMLElement ) . closest ( ".cm-line, .CodeChat-doc" ) ;
419433 // If it's a doc block, then tell Code Mirror not to handle this event.
420434 if ( ancestor ?. classList . contains ( "CodeChat-doc" ) ) {
421435 return ancestor as HTMLDivElement ;
@@ -442,12 +456,6 @@ const on_dirty = (
442456 const indent = indent_div . innerHTML ;
443457 const delimiter = indent_div . getAttribute ( "data-delimiter" ) ! ;
444458 const [ contents_div , is_tinymce ] = get_contents ( target ) ;
445- // Revert the typeset math to its original form.
446- window . MathJax . startup . document
447- . getMathItemsWithin ( contents_div )
448- . forEach ( ( item : any ) => {
449- item . removeFromDocument ( true ) ;
450- } ) ;
451459 tinymce_singleton ! . save ( ) ;
452460 const content = is_tinymce
453461 ? tinymce_singleton ! . getContent ( )
@@ -464,8 +472,6 @@ const on_dirty = (
464472
465473 current_view . dispatch ( { effects } ) ;
466474
467- // Re-typeset.
468- mathJaxTypeset ( contents_div ) ;
469475 return false ;
470476} ;
471477
@@ -480,8 +486,8 @@ const DocBlockPlugin = ViewPlugin.fromClass(
480486 // so it can be edited. A simpler alternative is to do this in the
481487 // update() method above, but this is VERY slow, since update is
482488 // called frequently.
483- focusin : ( event : Event , view : EditorView ) => {
484- const target_or_false = event_is_in_doc_block ( event ) ;
489+ focusin : ( event : FocusEvent , view : EditorView ) => {
490+ const target_or_false = element_is_in_doc_block ( event . target ) ;
485491 if ( ! target_or_false ) {
486492 return false ;
487493 }
@@ -514,7 +520,11 @@ const DocBlockPlugin = ViewPlugin.fromClass(
514520
515521 // See if this is already a TinyMCE instance; if not, move it
516522 // here.
517- if ( ! is_tinymce ) {
523+ if ( is_tinymce ) {
524+ ignore_next_dirty = true ;
525+ mathJaxUnTypeset ( contents_div ) ;
526+ ignore_next_dirty = false ;
527+ } else {
518528 // Wait until the focus event completes; this causes the
519529 // cursor position (the selection) to be set in the
520530 // contenteditable div. Then, save that location.
@@ -591,8 +601,15 @@ const DocBlockPlugin = ViewPlugin.fromClass(
591601 // Move TinyMCE to the new location, then remove the old
592602 // div it will replace.
593603 target . insertBefore ( tinymce_div , null ) ;
594- tinymce_singleton ! . setContent ( contents_div . innerHTML ) ;
604+ // TinyMCE edits booger MathJax. Also, the math is
605+ // uneditable. So, translate it back to its untypeset
606+ // form. When editing is done, it will be re-rendered.
607+ mathJaxUnTypeset ( contents_div ) ;
608+
609+ // Setting the content makes TinyMCE consider it dirty
610+ // -- ignore this "dirty" event.
595611 ignore_next_dirty = true ;
612+ tinymce_singleton ! . setContent ( contents_div . innerHTML ) ;
596613 tinymce_singleton ! . save ( ) ;
597614 contents_div . remove ( ) ;
598615
@@ -833,6 +850,27 @@ export const CodeMirror_load = async (
833850 }
834851 on_dirty ( target_or_false ) ;
835852 } ) ;
853+ // When leaving a TinyMCE block, retypeset the math. (It's
854+ // untypeset when entering the block, to avoid editing
855+ // problems.)
856+ editor . on ( "focusout" , ( event : any ) => {
857+ const target_or_false = event . target ;
858+ if ( target_or_false == null ) {
859+ return false ;
860+ }
861+ if ( ! tinymce_singleton ! . isDirty ( ) ) {
862+ ignore_next_dirty = true ;
863+ }
864+ // When switching from one doc block to another, the MathJax
865+ // typeset finishes after the new doc block has been
866+ // updated. To prevent saving the "dirty" content from
867+ // typesetting, wait until this finishes to clear the
868+ // `ignore_next_dirty` flag.
869+ mathJaxTypeset ( target_or_false , ( ) => {
870+ tinymce_singleton ! . save ( ) ;
871+ ignore_next_dirty = false ;
872+ } ) ;
873+ } )
836874 } ,
837875 } )
838876 ) [ 0 ] ;
0 commit comments