@@ -183,9 +183,14 @@ define(function (require, exports, module) {
183183 } ;
184184 ThemeManager . on ( "themeChange" , _themeChangeHandler ) ;
185185
186- // Listen for cursor activity in CM5 for scroll sync and selection sync (CM5 → iframe)
186+ // Listen for cursor activity in CM5 for scroll sync, selection sync, and toolbar state (CM5 → iframe)
187187 _cursorHandler = function ( ) {
188- if ( _syncingFromIframe || ! _iframeReady || ! _cursorSyncEnabled ) {
188+ if ( _syncingFromIframe || ! _iframeReady ) {
189+ return ;
190+ }
191+ // Toolbar state sync always runs (independent of cursor sync toggle)
192+ _syncToolbarStateToIframe ( ) ;
193+ if ( ! _cursorSyncEnabled ) {
189194 return ;
190195 }
191196 clearTimeout ( _scrollSyncTimer ) ;
@@ -509,6 +514,82 @@ define(function (require, exports, module) {
509514 } , "*" ) ;
510515 }
511516
517+ /**
518+ * Parse the current CM line to determine the block type and formatting context,
519+ * then send it to the iframe so the toolbar can reflect CM cursor position.
520+ */
521+ function _syncToolbarStateToIframe ( ) {
522+ if ( ! _active || ! _iframeReady ) {
523+ return ;
524+ }
525+ const iframeWindow = _getIframeWindow ( ) ;
526+ if ( ! iframeWindow ) {
527+ return ;
528+ }
529+ const cm = _getCM ( ) ;
530+ if ( ! cm ) {
531+ return ;
532+ }
533+ const cursor = cm . getCursor ( ) ;
534+ const lineText = cm . getLine ( cursor . line ) || "" ;
535+ const trimmed = lineText . trimStart ( ) ;
536+
537+ // Determine block type from markdown syntax
538+ let blockType = "P" ;
539+ if ( / ^ # { 1 } \s / . test ( trimmed ) ) { blockType = "H1" ; }
540+ else if ( / ^ # { 2 } \s / . test ( trimmed ) ) { blockType = "H2" ; }
541+ else if ( / ^ # { 3 } \s / . test ( trimmed ) ) { blockType = "H3" ; }
542+ else if ( / ^ # { 4 } \s / . test ( trimmed ) ) { blockType = "H4" ; }
543+ else if ( / ^ # { 5 } \s / . test ( trimmed ) ) { blockType = "H5" ; }
544+ else if ( / ^ # { 6 } \s / . test ( trimmed ) ) { blockType = "H6" ; }
545+
546+ // Check context by scanning surrounding lines
547+ let inList = / ^ \s * [ - * + ] \s / . test ( lineText ) || / ^ \s * \d + \. \s / . test ( lineText ) ;
548+ let inTable = lineText . trim ( ) . startsWith ( "|" ) && lineText . trim ( ) . endsWith ( "|" ) ;
549+ let inCodeBlock = false ;
550+
551+ // Check if we're inside a fenced code block by counting ``` above
552+ let fenceCount = 0 ;
553+ for ( let i = 0 ; i < cursor . line ; i ++ ) {
554+ if ( / ^ ` ` ` / . test ( cm . getLine ( i ) . trimStart ( ) ) ) {
555+ fenceCount ++ ;
556+ }
557+ }
558+ inCodeBlock = fenceCount % 2 === 1 ;
559+
560+ // Detect formatting around cursor
561+ let bold = false ;
562+ let italic = false ;
563+ let underline = false ;
564+ let strikethrough = false ;
565+
566+ // Simple inline format detection: check if cursor is within ** ** or * * etc.
567+ const beforeCursor = lineText . substring ( 0 , cursor . ch ) ;
568+ const afterCursor = lineText . substring ( cursor . ch ) ;
569+ const fullContext = beforeCursor + afterCursor ;
570+ // Count unescaped markers before cursor
571+ const boldBefore = ( beforeCursor . match ( / \* \* / g) || [ ] ) . length ;
572+ const italicBefore = ( beforeCursor . replace ( / \* \* / g, "" ) . match ( / \* / g) || [ ] ) . length ;
573+ bold = boldBefore % 2 === 1 ;
574+ italic = italicBefore % 2 === 1 ;
575+ strikethrough = ( beforeCursor . match ( / ~ ~ / g) || [ ] ) . length % 2 === 1 ;
576+
577+ iframeWindow . postMessage ( {
578+ type : "MDVIEWR_TOOLBAR_STATE" ,
579+ state : {
580+ blockType : blockType ,
581+ bold : bold ,
582+ italic : italic ,
583+ underline : underline ,
584+ strikethrough : strikethrough ,
585+ inTable : inTable ,
586+ inList : inList ,
587+ inCodeBlock : inCodeBlock ,
588+ inHeading : blockType !== "P"
589+ }
590+ } , "*" ) ;
591+ }
592+
512593 /**
513594 * Move the CM5 cursor to the given source line (1-based) and scroll
514595 * the editor to show it if it's not already visible.
0 commit comments