@@ -85,7 +85,12 @@ import { yaml } from "@codemirror/lang-yaml";
8585import { Editor , init , tinymce } from "./tinymce-config.mjs" ;
8686
8787// ### Local
88- import { set_is_dirty , startAutosaveTimer } from "./CodeChatEditor.mjs" ;
88+ import {
89+ set_is_dirty ,
90+ startAutosaveTimer ,
91+ saveSelection ,
92+ restoreSelection ,
93+ } from "./CodeChatEditor.mjs" ;
8994import {
9095 CodeChatForWeb ,
9196 CodeMirror ,
@@ -100,7 +105,6 @@ import { show_toast } from "./show_toast.mjs";
100105// Globals
101106// -----------------------------------------------------------------------------
102107let current_view : EditorView ;
103- let tinymce_singleton : Editor | undefined ;
104108// This indicates that a call to `on_dirty` is scheduled, but hasn't run yet.
105109let on_dirty_scheduled = false ;
106110
@@ -461,10 +465,10 @@ class DocBlockWidget extends WidgetType {
461465 if ( is_tinymce ) {
462466 // Save the cursor location before the update, then restore it
463467 // afterwards, if TinyMCE has focus.
464- const sel = tinymce_singleton ! . hasFocus ( )
468+ const sel = tinymce . activeEditor ! . hasFocus ( )
465469 ? saveSelection ( )
466470 : undefined ;
467- tinymce_singleton ! . setContent ( this . contents ) ;
471+ tinymce . activeEditor ! . setContent ( this . contents ) ;
468472 if ( sel !== undefined ) {
469473 restoreSelection ( sel ) ;
470474 }
@@ -504,102 +508,6 @@ class DocBlockWidget extends WidgetType {
504508 }
505509}
506510
507- const saveSelection = ( ) => {
508- // Changing the text inside TinyMCE causes it to loose a selection tied to a
509- // specific node. So, instead store the selection as an array of indices in
510- // the childNodes array of each element: for example, a given selection is
511- // element 10 of the root TinyMCE div's children (selecting an ol tag),
512- // element 5 of the ol's children (selecting the last li tag), element 0 of
513- // the li's children (a text node where the actual click landed; the offset
514- // in this node is placed in `selection_offset`.)
515- const sel = window . getSelection ( ) ;
516- const selection_path = [ ] ;
517- const selection_offset = sel ?. anchorOffset ;
518- if ( sel ?. anchorNode ) {
519- // Find a path from the selection back to the containing div.
520- for (
521- let current_node = sel . anchorNode , is_first = true ;
522- // Continue until we find the div which contains the doc block
523- // contents: either it's not an element (such as a div), ...
524- current_node . nodeType !== Node . ELEMENT_NODE ||
525- // or it's not the doc block contents div.
526- ( ! ( current_node as Element ) . classList . contains (
527- "CodeChat-doc-contents" ,
528- ) &&
529- // Sometimes, the parent of a custom node (`wc-mermaid`) skips the TinyMCE div and returns the overall div. I don't know why.
530- ! ( current_node as Element ) . classList . contains ( "CodeChat-doc" ) ) ;
531- current_node = current_node . parentNode ! , is_first = false
532- ) {
533- // Store the index of this node in its' parent list of child
534- // nodes/children. Use `childNodes` on the first iteration, since
535- // the selection is often in a text node, which isn't in the
536- // `parents` list. However, using `childNodes` all the time causes
537- // trouble when reversing the selection -- sometimes, the
538- // `childNodes` change based on whether text nodes (such as a
539- // newline) are included are not after tinyMCE parses the content.
540- const p = current_node . parentNode ;
541- // In case we go off the rails, give up if there are no more parents.
542- if ( p === null ) {
543- return {
544- selection_path : [ ] ,
545- selection_offset : 0 ,
546- } ;
547- }
548- selection_path . unshift (
549- Array . prototype . indexOf . call (
550- is_first ? p . childNodes : p . children ,
551- current_node ,
552- ) ,
553- ) ;
554- }
555- }
556- return { selection_path, selection_offset } ;
557- } ;
558-
559- // Restore the selection produced by `saveSelection` to the active TinyMCE
560- // instance.
561- const restoreSelection = ( {
562- selection_path,
563- selection_offset,
564- } : {
565- selection_path : number [ ] ;
566- selection_offset ?: number ;
567- } ) => {
568- // Copy the selection over to TinyMCE by indexing the selection path to find
569- // the selected node.
570- if ( selection_path . length && typeof selection_offset === "number" ) {
571- let selection_node = tinymce_singleton ! . getContentAreaContainer ( ) ;
572- for (
573- ;
574- selection_path . length &&
575- // If something goes wrong, bail out instead of producing
576- // exceptions.
577- selection_node !== undefined ;
578- selection_node =
579- // As before, use the more-consistent `children` except for the
580- // last element, where we might be selecting a `text` node.
581- (
582- selection_path . length > 1
583- ? selection_node . children
584- : selection_node . childNodes
585- ) [ selection_path . shift ( ) ! ] ! as HTMLElement
586- ) ;
587- // Exit on failure.
588- if ( selection_node === undefined ) {
589- return ;
590- }
591- // Use that to set the selection.
592- tinymce_singleton ! . selection . setCursorLocation (
593- selection_node ,
594- // In case of edits, avoid an offset past the end of the node.
595- Math . min (
596- selection_offset ,
597- selection_node . nodeValue ?. length ?? Number . MAX_VALUE ,
598- ) ,
599- ) ;
600- }
601- } ;
602-
603511// Typeset the provided node; taken from the
604512// [MathJax docs](https://docs.mathjax.org/en/latest/web/typeset.html#handling-asynchronous-typesetting).
605513export const mathJaxTypeset = async (
@@ -700,7 +608,7 @@ const on_dirty = (
700608 // the actual div. But I don't know how.
701609 mathJaxUnTypeset ( contents_div ) ;
702610 const contents = is_tinymce
703- ? tinymce_singleton ! . save ( )
611+ ? tinymce . activeEditor ! . save ( )
704612 : contents_div . innerHTML ;
705613 await mathJaxTypeset ( contents_div ) ;
706614 current_view . dispatch ( {
@@ -847,7 +755,7 @@ export const DocBlockPlugin = ViewPlugin.fromClass(
847755 old_contents_div . className = "CodeChat-doc-contents" ;
848756 old_contents_div . contentEditable = "true" ;
849757 old_contents_div . replaceChildren (
850- ...tinymce_singleton ! . getContentAreaContainer ( )
758+ ...tinymce . activeEditor ! . getContentAreaContainer ( )
851759 . childNodes ,
852760 ) ;
853761 tinymce_div . parentNode ! . insertBefore (
@@ -863,15 +771,17 @@ export const DocBlockPlugin = ViewPlugin.fromClass(
863771
864772 // Setting the content makes TinyMCE consider it dirty
865773 // -- ignore this "dirty" event.
866- tinymce_singleton ! . setContent ( contents_div . innerHTML ) ;
774+ tinymce . activeEditor ! . setContent (
775+ contents_div . innerHTML ,
776+ ) ;
867777 contents_div . remove ( ) ;
868778 // The new div is now a TinyMCE editor. Retypeset this.
869779 await mathJaxTypeset ( tinymce_div ) ;
870780
871781 // This process causes TinyMCE to lose focus. Restore
872782 // that. However, this causes TinyMCE to lose the
873783 // selection, which the next bit of code then restores.
874- tinymce_singleton ! . focus ( false ) ;
784+ tinymce . activeEditor ! . focus ( false ) ;
875785
876786 // Copy the selection over to TinyMCE by indexing the
877787 // selection path to find the selected node.
@@ -1091,22 +1001,21 @@ export const CodeMirror_load = async (
10911001 state,
10921002 scrollTo : scrollSnapshot ,
10931003 } ) ;
1094- tinymce_singleton = (
1095- await init ( {
1096- selector : "#TinyMCE-inst" ,
1097- setup : ( editor : Editor ) => {
1098- // See the
1099- // [docs](https://www.tiny.cloud/docs/tinymce/latest/events/#editor-core-events).
1100- editor . on ( "input" , ( event : Event ) => {
1101- const target_or_false = event . target as HTMLElement ;
1102- if ( target_or_false == null ) {
1103- return ;
1104- }
1105- setTimeout ( ( ) => on_dirty ( target_or_false ) ) ;
1106- } ) ;
1107- } ,
1108- } )
1109- ) [ 0 ] ;
1004+
1005+ await init ( {
1006+ selector : "#TinyMCE-inst" ,
1007+ setup : ( editor : Editor ) => {
1008+ // See the
1009+ // [docs](https://www.tiny.cloud/docs/tinymce/latest/events/#editor-core-events).
1010+ editor . on ( "input" , ( event : Event ) => {
1011+ const target_or_false = event . target as HTMLElement ;
1012+ if ( target_or_false == null ) {
1013+ return ;
1014+ }
1015+ setTimeout ( ( ) => on_dirty ( target_or_false ) ) ;
1016+ } ) ;
1017+ } ,
1018+ } ) ;
11101019 } else {
11111020 // This contains a diff, instead of plain text. Apply the text diff.
11121021 //
0 commit comments