@@ -61,6 +61,7 @@ document.addEventListener("DOMContentLoaded", function () {
6161 let syncScrollingEnabled = true ;
6262 let isEditorScrolling = false ;
6363 let isPreviewScrolling = false ;
64+ let isProgrammaticScrolling = false ;
6465 let scrollSyncTimeout = null ;
6566 const SCROLL_SYNC_DELAY = 10 ;
6667
@@ -931,6 +932,20 @@ document.addEventListener("DOMContentLoaded", function () {
931932 return `<pre><code class="hljs ${ validLanguage } ">${ highlightedCode } </code></pre>` ;
932933 } ;
933934
935+ renderer . heading = function ( text , level , raw ) {
936+ let id = raw
937+ . toLowerCase ( )
938+ . trim ( )
939+ . replace ( / < [ ^ > ] * > / g, '' )
940+ . replace ( / \s + / g, '-' )
941+ . replace ( / [ ^ \w - ] / g, '' )
942+ . replace ( / - + / g, '-' ) ;
943+ if ( ! id ) {
944+ id = 'heading-' + Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) ;
945+ }
946+ return `<h${ level } id="${ id } ">${ text } </h${ level } >` ;
947+ } ;
948+
934949 marked . use ( {
935950 extensions : [
936951 blockMathExtension ,
@@ -2921,7 +2936,7 @@ document.addEventListener("DOMContentLoaded", function () {
29212936 }
29222937
29232938 function syncEditorToPreview ( ) {
2924- if ( ! syncScrollingEnabled || isPreviewScrolling ) return ;
2939+ if ( ! syncScrollingEnabled || isPreviewScrolling || isProgrammaticScrolling ) return ;
29252940 isEditorScrolling = true ;
29262941
29272942 if ( scrollSyncTimeout ) cancelAnimationFrame ( scrollSyncTimeout ) ;
@@ -2944,7 +2959,7 @@ document.addEventListener("DOMContentLoaded", function () {
29442959 }
29452960
29462961 function syncPreviewToEditor ( ) {
2947- if ( ! syncScrollingEnabled || isEditorScrolling ) return ;
2962+ if ( ! syncScrollingEnabled || isEditorScrolling || isProgrammaticScrolling ) return ;
29482963 isPreviewScrolling = true ;
29492964
29502965 if ( scrollSyncTimeout ) cancelAnimationFrame ( scrollSyncTimeout ) ;
@@ -6583,12 +6598,33 @@ document.addEventListener("DOMContentLoaded", function () {
65836598 .markdown-body {
65846599 box-sizing: border-box;
65856600 min-width: 200px;
6586- max-width: 980px;
6601+ max-width: 100%;
6602+ width: fit-content;
65876603 margin: 0 auto;
65886604 padding: 45px;
65896605 background-color: ${ isDarkTheme ? "#0d1117" : "#ffffff" } ;
65906606 color: ${ isDarkTheme ? "#c9d1d9" : "#24292e" } ;
65916607 }
6608+ .markdown-body > p,
6609+ .markdown-body > ul,
6610+ .markdown-body > ol,
6611+ .markdown-body > blockquote,
6612+ .markdown-body > h1,
6613+ .markdown-body > h2,
6614+ .markdown-body > h3,
6615+ .markdown-body > h4,
6616+ .markdown-body > h5,
6617+ .markdown-body > h6,
6618+ .markdown-body > pre,
6619+ .markdown-body > table,
6620+ .markdown-body > details,
6621+ .markdown-body > dl,
6622+ .markdown-body > hr {
6623+ max-width: 980px;
6624+ margin-left: auto !important;
6625+ margin-right: auto !important;
6626+ }
6627+
65926628
65936629 /* Syntax Highlighting */
65946630 .hljs-doctag, .hljs-keyword, .hljs-template-tag, .hljs-template-variable, .hljs-type, .hljs-variable.language_ { color: ${ isDarkTheme ? "#ff7b72" : "#d73a49" } ; }
@@ -9646,7 +9682,53 @@ document.addEventListener("DOMContentLoaded", function () {
96469682 const href = link . getAttribute ( 'href' ) ;
96479683 if ( href ) {
96489684 if ( href . startsWith ( '#' ) ) {
9649- return ; // Allow internal anchor navigation
9685+ const targetId = decodeURIComponent ( href . slice ( 1 ) ) ;
9686+ let targetEl = null ;
9687+ if ( targetId ) {
9688+ try {
9689+ targetEl = markdownPreview . querySelector ( `[id="${ CSS . escape ( targetId ) } "]` ) ||
9690+ markdownPreview . querySelector ( `[name="${ CSS . escape ( targetId ) } "]` ) ;
9691+ } catch ( err ) {
9692+ targetEl = Array . from ( markdownPreview . querySelectorAll ( '[id], [name]' ) ) . find ( el => {
9693+ return el . getAttribute ( 'id' ) === targetId || el . getAttribute ( 'name' ) === targetId ;
9694+ } ) ;
9695+ }
9696+
9697+ if ( ! targetEl ) {
9698+ const cleanTargetId = targetId . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 ] / g, '' ) ;
9699+ if ( cleanTargetId ) {
9700+ targetEl = Array . from ( markdownPreview . querySelectorAll ( 'h1, h2, h3, h4, h5, h6' ) ) . find ( heading => {
9701+ const cleanText = heading . textContent . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 ] / g, '' ) ;
9702+ return cleanText === cleanTargetId ;
9703+ } ) ;
9704+ }
9705+ }
9706+ }
9707+ if ( targetEl ) {
9708+ e . preventDefault ( ) ;
9709+ isProgrammaticScrolling = true ;
9710+
9711+ // Scroll preview pane to target heading
9712+ targetEl . scrollIntoView ( { behavior : 'smooth' , block : 'start' } ) ;
9713+
9714+ // Scroll editor pane to the matching synced position
9715+ const previewScrollRange = previewPane . scrollHeight - previewPane . clientHeight ;
9716+ const targetRatio = previewScrollRange > 0 ? Math . min ( 1 , Math . max ( 0 , targetEl . offsetTop / previewScrollRange ) ) : 0 ;
9717+ const editorScrollPosition = targetRatio * ( markdownEditor . scrollHeight - markdownEditor . clientHeight ) ;
9718+
9719+ markdownEditor . scrollTo ( {
9720+ top : editorScrollPosition ,
9721+ behavior : 'smooth'
9722+ } ) ;
9723+
9724+ if ( window . programmaticScrollTimeout ) {
9725+ clearTimeout ( window . programmaticScrollTimeout ) ;
9726+ }
9727+ window . programmaticScrollTimeout = setTimeout ( ( ) => {
9728+ isProgrammaticScrolling = false ;
9729+ } , 1000 ) ;
9730+ }
9731+ return ;
96509732 }
96519733
96529734 e . preventDefault ( ) ;
0 commit comments