@@ -88,7 +88,7 @@ export interface SelectionCursorContext {
8888
8989export const ToolbarExtension = Extension . create < ToolbarOptions > ( {
9090 addProseMirrorPlugins ( ) {
91- return [ ToolbarControlPlugin ( this . editor , this . options ) ]
91+ return [ LoadingStatePlugin ( ) , ToolbarControlPlugin ( this . editor , this . options ) ]
9292 }
9393} )
9494
@@ -98,6 +98,67 @@ export interface ToolbarControlTxMeta {
9898 providers ?: Array < ToolbarProvider < any > >
9999}
100100
101+ export const loadingStatePluginKey = new PluginKey ( 'loadingState' )
102+
103+ export interface LoadingStatePluginState {
104+ loadingNodes : Set < number >
105+ }
106+
107+ export interface LoadingStateTxMeta {
108+ setLoading ?: { pos : number , loading : boolean }
109+ }
110+
111+ export function LoadingStatePlugin ( ) : Plugin < LoadingStatePluginState > {
112+ return new Plugin < LoadingStatePluginState > ( {
113+ key : loadingStatePluginKey ,
114+
115+ state : {
116+ init : ( ) => ( {
117+ loadingNodes : new Set < number > ( )
118+ } ) ,
119+
120+ apply ( tr , prevState ) {
121+ const meta = tr . getMeta ( loadingStatePluginKey ) as LoadingStateTxMeta | undefined
122+ let loadingNodes = prevState . loadingNodes
123+
124+ // Handle loading state changes
125+ if ( meta ?. setLoading !== undefined ) {
126+ loadingNodes = new Set ( loadingNodes )
127+ if ( meta . setLoading . loading ) {
128+ loadingNodes . add ( meta . setLoading . pos )
129+ } else {
130+ loadingNodes . delete ( meta . setLoading . pos )
131+ }
132+ }
133+
134+ // Map positions through document changes
135+ if ( tr . docChanged && loadingNodes . size > 0 ) {
136+ const mapped = new Set < number > ( )
137+ loadingNodes . forEach ( ( pos ) => {
138+ try {
139+ const mappedPos = tr . mapping . map ( pos , - 1 )
140+ // Verify position is still valid
141+ if ( mappedPos >= 0 && mappedPos < tr . doc . content . size ) {
142+ mapped . add ( mappedPos )
143+ }
144+ } catch ( e ) {
145+ // Position no longer valid, skip it
146+ console . debug ( 'LoadingStatePlugin: position no longer valid' , pos )
147+ }
148+ } )
149+ loadingNodes = mapped
150+ }
151+
152+ return { loadingNodes }
153+ }
154+ }
155+ } )
156+ }
157+
158+ export function getLoadingState ( state : EditorState ) : LoadingStatePluginState | undefined {
159+ return loadingStatePluginKey . getState ( state ) as LoadingStatePluginState | undefined
160+ }
161+
101162export const toolbarPluginKey = new PluginKey ( 'dynamicToolbar' )
102163
103164export interface ToolbarControlPluginState {
@@ -326,6 +387,7 @@ export function ToolbarControlPlugin (editor: Editor, options: ToolbarOptions):
326387 editor . on ( 'transaction' , ( { editor, transaction : tr } ) => {
327388 const meta = tr . getMeta ( toolbarPluginKey ) as ToolbarControlTxMeta
328389 const loadingState = tr . getMeta ( 'loadingState' ) as boolean | undefined
390+
329391 if ( meta ?. cursor !== undefined && ! eqCursors ( currCursor , meta . cursor ) ) {
330392 prevCursor = currCursor
331393 currCursor = meta . cursor !== null ? { ...meta . cursor } : null
@@ -506,17 +568,22 @@ function updateCursorFromMouseEvent (view: EditorView, event: MouseEvent): void
506568}
507569
508570function scanForLoadingState ( view : EditorView , range : Range ) : boolean {
509- let isLoading = false
510- view . state . doc . nodesBetween ( range . from , range . to , ( node , pos ) => {
511- const element = view . nodeDOM ( pos )
512- if ( ! ( element instanceof HTMLElement ) ) return
571+ if ( range . from > range . to || range . from < 0 ) {
572+ return false
573+ }
513574
514- if ( element . dataset . loading === 'true' ) {
515- isLoading = true
516- return false
575+ const loadingState = getLoadingState ( view . state )
576+ if ( loadingState === undefined || loadingState . loadingNodes . size === 0 ) {
577+ return false
578+ }
579+
580+ for ( const pos of loadingState . loadingNodes ) {
581+ if ( pos >= range . from && pos <= range . to ) {
582+ return true
517583 }
518- } )
519- return isLoading
584+ }
585+
586+ return false
520587}
521588
522589function scanForAnchor ( view : EditorView , range : Range ) : HTMLElement | undefined {
@@ -653,15 +720,12 @@ function minmax (value = 0, min = 0, max = 0): number {
653720 return Math . min ( Math . max ( value , min ) , max )
654721}
655722
656- export function setLoadingState ( view : EditorView , element : HTMLElement , loading : boolean ) : void {
657- if ( loading ) {
658- element . setAttribute ( 'data-loading' , 'true' )
659- } else {
660- element . removeAttribute ( 'data-loading' )
661- requestAnimationFrame ( ( ) => {
662- view . dispatch ( view . state . tr . setMeta ( 'loadingState' , loading ) )
663- } )
723+ export function setLoadingState ( view : EditorView , pos : number , loading : boolean ) : void {
724+ const meta : LoadingStateTxMeta = {
725+ setLoading : { pos, loading }
664726 }
727+
728+ view . dispatch ( view . state . tr . setMeta ( loadingStatePluginKey , meta ) . setMeta ( 'loadingState' , loading ) )
665729}
666730
667731export const GeneralToolbarProvider : ToolbarProvider < any > = {
0 commit comments