@@ -104,6 +104,8 @@ let tinymce_singleton: Editor | undefined;
104104// When true, don't update on the next call to `on_dirty`. See that function for
105105// more info.
106106let ignore_next_dirty = false ;
107+ // This indicates that a call to `on_dirty` is scheduled, but hasn't run yet.
108+ let on_dirty_scheduled = false ;
107109// True to ignore the next text selection change, since updates to the cursor or
108110// scroll position from the Client trigged this change.
109111let ignore_selection_change = false ;
@@ -436,6 +438,7 @@ class DocBlockWidget extends WidgetType {
436438 `<div class="CodeChat-doc-contents" spellcheck contenteditable>` +
437439 this . contents +
438440 "</div>" ;
441+ // TODO: this is an async call. However, CodeMirror doesn't provide async support.
439442 mathJaxTypeset ( wrap ) ;
440443 return wrap ;
441444 }
@@ -457,16 +460,17 @@ class DocBlockWidget extends WidgetType {
457460 // The contents div could be a TinyMCE instance, or just a plain div.
458461 // Handle both cases.
459462 const [ contents_div , is_tinymce ] = get_contents ( dom ) ;
463+ window . MathJax . typesetClear ( contents_div ) ;
460464 if ( is_tinymce ) {
461465 ignore_next_dirty = true ;
462466 tinymce_singleton ! . setContent ( this . contents ) ;
463467 tinymce_singleton ! . save ( ) ;
464468 } else {
465469 contents_div . innerHTML = this . contents ;
466- mathJaxTypeset ( contents_div ) ;
467470 }
471+ mathJaxTypeset ( contents_div ) ;
468472
469- // Indicate the update was successful.
473+ // Indicate the update was successful. TODO: but, contents are still pending...
470474 return true ;
471475 }
472476
@@ -501,17 +505,9 @@ class DocBlockWidget extends WidgetType {
501505export const mathJaxTypeset = async (
502506 // The node to typeset.
503507 node : HTMLElement ,
504- // An optional function to run when the typeset finishes.
505- afterTypesetFunc : ( ) => void = ( ) => { } ,
506508) => {
507- // Don't await this promise -- other MathJax processing may still be
508- // running. See the
509- // [release notes](https://github.com/mathjax/MathJax-src/releases/tag/4.0.0-rc.4#api).
510- window . MathJax . typesetPromise ( [ node ] ) ;
511509 try {
512- // Instead, this function calls `afterTypesetFunc` after it awaits all
513- // internal MathJax promises.
514- window . MathJax . whenReady ( afterTypesetFunc ) ;
510+ await window . MathJax . typesetPromise ( [ node ] ) ;
515511 } catch ( err : any ) {
516512 report_error ( `Typeset failed: ${ err . message } ` ) ;
517513 }
@@ -576,40 +572,57 @@ const on_dirty = (
576572 ignore_next_dirty = false ;
577573 return ;
578574 }
579- // Find the doc block parent div.
580- const target = ( event_target as HTMLDivElement ) . closest (
581- ".CodeChat-doc" ,
582- ) ! as HTMLDivElement ;
583575
584- // We can only get the position (the `from` value) for the doc block. Use
585- // this to find the `to` value for the doc block.
586- const from = current_view . posAtDOM ( target ) ;
587- // Send an update to the state field associated with this DOM element.
588- const indent_div = target . childNodes [ 0 ] as HTMLDivElement ;
589- const indent = indent_div . innerHTML ;
590- const delimiter = indent_div . getAttribute ( "data-delimiter" ) ! ;
591- const [ contents_div , is_tinymce ] = get_contents ( target ) ;
592- // Sorta ugly hack: TinyMCE stores its date in the DOM. CodeMirror stores
593- // state in external structs. We need to update the CodeMirror state, but
594- // not overwrite the DOM with this "new" state, since the DOM is already
595- // updated. So, signal that this "update" is already done.
596- ( target as any ) . update_complete = true ;
597- tinymce_singleton ! . save ( ) ;
598- const contents = is_tinymce
599- ? tinymce_singleton ! . getContent ( )
600- : contents_div . innerHTML ;
601- let effects : StateEffect < updateDocBlockType > [ ] = [
602- updateDocBlock . of ( {
603- from,
604- indent,
605- delimiter,
606- contents,
607- } ) ,
608- ] ;
576+ if ( on_dirty_scheduled ) {
577+ return ;
578+ }
609579
610- current_view . dispatch ( { effects } ) ;
580+ // Only run this after typesetting is done.
581+ window . MathJax . whenReady ( ( ) => {
582+ // Find the doc block parent div.
583+ const target = ( event_target as HTMLDivElement ) . closest (
584+ ".CodeChat-doc" ,
585+ ) ! as HTMLDivElement ;
611586
612- return false ;
587+ // We can only get the position (the `from` value) for the doc block. Use
588+ // this to find the `to` value for the doc block.
589+ let from ;
590+ try {
591+ from = current_view . posAtDOM ( target ) ;
592+ } catch ( e ) {
593+ console . error ( "Unable to get position from DOM." , target ) ;
594+ return ;
595+ }
596+ // Send an update to the state field associated with this DOM element.
597+ const indent_div = target . childNodes [ 0 ] as HTMLDivElement ;
598+ const indent = indent_div . innerHTML ;
599+ const delimiter = indent_div . getAttribute ( "data-delimiter" ) ! ;
600+ const [ contents_div , is_tinymce ] = get_contents ( target ) ;
601+ // I'd like to extract this string, then untypeset only that string, not the actual div. But I don't know how.
602+ mathJaxUnTypeset ( contents_div ) ;
603+ const contents = is_tinymce
604+ ? tinymce_singleton ! . save ( )
605+ : contents_div . innerHTML ;
606+ // Although this is async, nothing following this call depends on its completion.
607+ mathJaxTypeset ( contents_div ) ;
608+ // Sorta ugly hack: TinyMCE stores its data in the DOM. CodeMirror stores
609+ // state in external structs. We need to update the CodeMirror state, but
610+ // not overwrite the DOM with this "new" state, since the DOM is already
611+ // updated. So, signal that this "update" is already done.
612+ ( target as any ) . update_complete = true ;
613+ let effects : StateEffect < updateDocBlockType > [ ] = [
614+ updateDocBlock . of ( {
615+ from,
616+ indent,
617+ delimiter,
618+ contents,
619+ } ) ,
620+ ] ;
621+
622+ current_view . dispatch ( { effects } ) ;
623+
624+ on_dirty_scheduled = false ;
625+ } ) ;
613626} ;
614627
615628export const DocBlockPlugin = ViewPlugin . fromClass (
@@ -714,17 +727,17 @@ export const DocBlockPlugin = ViewPlugin.fromClass(
714727 // See if this is already a TinyMCE instance; if not, move it
715728 // here.
716729 if ( is_tinymce ) {
717- ignore_next_dirty = true ;
718- mathJaxUnTypeset ( contents_div ) ;
719- // If there was no math to untypeset, then `on_dirty` wasn't
720- // called, but we should no longer ignore the next dirty
721- // flag.
722- ignore_next_dirty = false ;
730+ // Nothing to do.
723731 } else {
724732 // Wait until the focus event completes; this causes the
725733 // cursor position (the selection) to be set in the
726734 // contenteditable div. Then, save that location.
727735 setTimeout ( ( ) => {
736+ // Untypeset math in the old doc block and the current doc block before moving its contents around.
737+ const tinymce_div =
738+ document . getElementById ( "TinyMCE-inst" ) ! ;
739+ mathJaxUnTypeset ( tinymce_div ) ;
740+ mathJaxUnTypeset ( contents_div ) ;
728741 // The code which moves TinyMCE into this div disturbs
729742 // all the nodes, which causes it to loose a selection
730743 // tied to a specific node. So, instead store the
@@ -779,8 +792,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass(
779792 // With the selection saved, it's safe to replace the
780793 // contenteditable div with the TinyMCE instance (which
781794 // would otherwise wipe the selection).
782- const tinymce_div =
783- document . getElementById ( "TinyMCE-inst" ) ! ;
795+ //
784796 // Copy the current TinyMCE instance contents into a
785797 // contenteditable div.
786798 const old_contents_div = document . createElement ( "div" ) ! ;
@@ -794,20 +806,20 @@ export const DocBlockPlugin = ViewPlugin.fromClass(
794806 old_contents_div ,
795807 null ,
796808 ) ;
809+ // The previous content edited by TinyMCE is now a div. Retypeset this after the transition.
810+ mathJaxTypeset ( old_contents_div ) ;
797811 // Move TinyMCE to the new location, then remove the old
798812 // div it will replace.
799813 target . insertBefore ( tinymce_div , null ) ;
800- // TinyMCE edits booger MathJax. Also, the math is
801- // uneditable. So, translate it back to its untypeset
802- // form. When editing is done, it will be re-rendered.
803- mathJaxUnTypeset ( contents_div ) ;
804814
805815 // Setting the content makes TinyMCE consider it dirty
806816 // -- ignore this "dirty" event.
807817 ignore_next_dirty = true ;
808818 tinymce_singleton ! . setContent ( contents_div . innerHTML ) ;
809819 tinymce_singleton ! . save ( ) ;
810820 contents_div . remove ( ) ;
821+ // The new div is now a TinyMCE editor. Retypeset this.
822+ mathJaxTypeset ( tinymce_div ) ;
811823
812824 // This process causes TinyMCE to lose focus. Restore
813825 // that. However, this causes TinyMCE to lose the
@@ -824,7 +836,9 @@ export const DocBlockPlugin = ViewPlugin.fromClass(
824836 tinymce_singleton ! . getContentAreaContainer ( ) ;
825837 for (
826838 ;
827- selection_path . length ;
839+ selection_path . length &&
840+ // If something goes wrong, bail out instead of producing exceptions.
841+ selection_node !== undefined ;
828842 selection_node =
829843 // As before, use the more-consistent
830844 // `children` except for the last element,
@@ -1054,38 +1068,16 @@ export const CodeMirror_load = async (
10541068 await init ( {
10551069 selector : "#TinyMCE-inst" ,
10561070 setup : ( editor : Editor ) => {
1071+ // See the [docs](https://www.tiny.cloud/docs/tinymce/latest/events/#editor-core-events).
10571072 editor . on ( "Dirty" , ( event : any ) => {
10581073 // Get the div TinyMCE stores edits in. TODO: find
1059- // documentation for this .
1074+ // documentation for `event.target.bodyElement` .
10601075 const target_or_false = event . target ?. bodyElement ;
10611076 if ( target_or_false == null ) {
10621077 return false ;
10631078 }
1064- on_dirty ( target_or_false ) ;
1065- } ) ;
1066- // When leaving a TinyMCE block, retypeset the math. (It's
1067- // untypeset when entering the block, to avoid editing
1068- // problems.)
1069- editor . on ( "focusout" , ( event : any ) => {
1070- const target_or_false = event . target ;
1071- if ( target_or_false == null ) {
1072- return false ;
1073- }
1074- // If the editor is dirty, save it first before we
1075- // possibly modify it.
1076- if ( tinymce_singleton ! . isDirty ( ) ) {
1077- tinymce_singleton ! . save ( ) ;
1078- }
1079- // When switching from one doc block to another, the
1080- // MathJax typeset finishes after the new doc block has
1081- // been updated. To prevent saving the "dirty" content
1082- // from typesetting, wait until this finishes to clear
1083- // the `ignore_next_dirty` flag.
1084- ignore_next_dirty = true ;
1085- mathJaxTypeset ( target_or_false , ( ) => {
1086- tinymce_singleton ! . save ( ) ;
1087- ignore_next_dirty = false ;
1088- } ) ;
1079+ // For some reason, calling this directly omits the most recent edit. Earlier versions of the code didn't have this problem. ???
1080+ setTimeout ( ( ) => on_dirty ( target_or_false ) ) ;
10891081 } ) ;
10901082 } ,
10911083 } )
0 commit comments