@@ -4659,6 +4659,8 @@ document.addEventListener("DOMContentLoaded", function () {
46594659 }
46604660
46614661 function updateFindHighlights ( ) {
4662+ updatePreviewFindHighlights ( ) ;
4663+
46624664 if ( ! editorHighlightLayer ) return ;
46634665 if ( ! isEditorVisible ( ) ) return ;
46644666 if ( ! isFindModalOpen || ! findReplaceInput || ! findReplaceInput . value || ! findMatches . length ) {
@@ -4687,6 +4689,157 @@ document.addEventListener("DOMContentLoaded", function () {
46874689 editorHighlightLayer . scrollLeft = scrollLeft ;
46884690 }
46894691
4692+ let previewHighlights = [ ] ;
4693+ let activePreviewHighlightIndex = - 1 ;
4694+
4695+ function isPreviewVisible ( ) {
4696+ return currentViewMode === 'preview' || currentViewMode === 'split' ;
4697+ }
4698+
4699+ function clearPreviewFindHighlights ( ) {
4700+ if ( ! markdownPreview ) return ;
4701+ const highlights = markdownPreview . querySelectorAll ( '.preview-find-highlight' ) ;
4702+ highlights . forEach ( function ( el ) {
4703+ const parent = el . parentNode ;
4704+ if ( parent ) {
4705+ parent . replaceChild ( document . createTextNode ( el . textContent ) , el ) ;
4706+ }
4707+ } ) ;
4708+ markdownPreview . normalize ( ) ;
4709+ }
4710+
4711+ function highlightPreviewText ( node , regex ) {
4712+ if ( ! node ) return ;
4713+ if ( node . nodeType === Node . TEXT_NODE ) {
4714+ const val = node . nodeValue ;
4715+ if ( ! val ) return ;
4716+
4717+ regex . lastIndex = 0 ;
4718+ let match ;
4719+ const matches = [ ] ;
4720+ while ( ( match = regex . exec ( val ) ) !== null ) {
4721+ if ( match [ 0 ] . length === 0 ) {
4722+ regex . lastIndex ++ ;
4723+ continue ;
4724+ }
4725+ matches . push ( {
4726+ start : match . index ,
4727+ end : match . index + match [ 0 ] . length ,
4728+ text : match [ 0 ]
4729+ } ) ;
4730+ }
4731+
4732+ if ( matches . length > 0 ) {
4733+ const parent = node . parentNode ;
4734+ if ( ! parent ) return ;
4735+
4736+ const fragment = document . createDocumentFragment ( ) ;
4737+ let lastIdx = 0 ;
4738+
4739+ matches . forEach ( function ( m ) {
4740+ if ( m . start > lastIdx ) {
4741+ fragment . appendChild ( document . createTextNode ( val . slice ( lastIdx , m . start ) ) ) ;
4742+ }
4743+ const mark = document . createElement ( 'mark' ) ;
4744+ mark . className = 'preview-find-highlight' ;
4745+ mark . textContent = m . text ;
4746+ fragment . appendChild ( mark ) ;
4747+ lastIdx = m . end ;
4748+ } ) ;
4749+
4750+ if ( lastIdx < val . length ) {
4751+ fragment . appendChild ( document . createTextNode ( val . slice ( lastIdx ) ) ) ;
4752+ }
4753+
4754+ parent . replaceChild ( fragment , node ) ;
4755+ }
4756+ } else if ( node . nodeType === Node . ELEMENT_NODE ) {
4757+ const tagName = node . tagName . toLowerCase ( ) ;
4758+ if ( tagName === 'script' || tagName === 'style' || tagName === 'textarea' || tagName === 'noscript' || tagName === 'svg' ) {
4759+ return ;
4760+ }
4761+ if ( node . classList . contains ( 'mermaid' ) || node . classList . contains ( 'mjx-container' ) || node . closest ( '.mermaid' ) || node . closest ( '.mjx-container' ) ) {
4762+ return ;
4763+ }
4764+
4765+ const children = Array . from ( node . childNodes ) ;
4766+ children . forEach ( function ( child ) {
4767+ highlightPreviewText ( child , regex ) ;
4768+ } ) ;
4769+ }
4770+ }
4771+
4772+ function updatePreviewFindHighlights ( ) {
4773+ clearPreviewFindHighlights ( ) ;
4774+ previewHighlights = [ ] ;
4775+
4776+ if ( ! isFindModalOpen || ! findReplaceInput || ! findReplaceInput . value || ! isPreviewVisible ( ) ) {
4777+ return ;
4778+ }
4779+
4780+ const query = findReplaceInput . value ;
4781+ const isRegex = document . getElementById ( 'find-regex' ) . classList . contains ( 'active' ) ;
4782+ const isCaseSensitive = document . getElementById ( 'find-case' ) . classList . contains ( 'active' ) ;
4783+ const isWholeWord = document . getElementById ( 'find-word' ) . classList . contains ( 'active' ) ;
4784+
4785+ let regex ;
4786+ try {
4787+ let pattern = isRegex ? query : query . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
4788+ if ( isWholeWord ) {
4789+ pattern = `\\b${ pattern } \\b` ;
4790+ }
4791+ const flags = isCaseSensitive ? 'g' : 'gi' ;
4792+ regex = new RegExp ( pattern , flags ) ;
4793+ } catch ( e ) {
4794+ return ;
4795+ }
4796+
4797+ highlightPreviewText ( markdownPreview , regex ) ;
4798+ previewHighlights = Array . from ( markdownPreview . querySelectorAll ( '.preview-find-highlight' ) ) ;
4799+ updateActivePreviewHighlight ( ) ;
4800+ }
4801+
4802+ function updateActivePreviewHighlight ( ) {
4803+ previewHighlights . forEach ( function ( el ) {
4804+ el . classList . remove ( 'active' ) ;
4805+ } ) ;
4806+
4807+ if ( ! previewHighlights . length ) {
4808+ activePreviewHighlightIndex = - 1 ;
4809+ return ;
4810+ }
4811+
4812+ if ( findMatches . length > 0 && activeFindIndex >= 0 ) {
4813+ const ratio = activeFindIndex / findMatches . length ;
4814+ activePreviewHighlightIndex = Math . min (
4815+ previewHighlights . length - 1 ,
4816+ Math . floor ( ratio * previewHighlights . length )
4817+ ) ;
4818+ } else {
4819+ activePreviewHighlightIndex = 0 ;
4820+ }
4821+
4822+ if ( activePreviewHighlightIndex >= 0 && activePreviewHighlightIndex < previewHighlights . length ) {
4823+ const activeEl = previewHighlights [ activePreviewHighlightIndex ] ;
4824+ activeEl . classList . add ( 'active' ) ;
4825+ scrollPreviewHighlightIntoView ( activeEl ) ;
4826+ }
4827+ }
4828+
4829+ function scrollPreviewHighlightIntoView ( element ) {
4830+ if ( ! element || ! previewPane ) return ;
4831+ const paneRect = previewPane . getBoundingClientRect ( ) ;
4832+ const elemRect = element . getBoundingClientRect ( ) ;
4833+ const isVisible = (
4834+ elemRect . top >= paneRect . top + 40 &&
4835+ elemRect . bottom <= paneRect . bottom - 40
4836+ ) ;
4837+ if ( ! isVisible ) {
4838+ const scrollTop = previewPane . scrollTop + ( elemRect . top - paneRect . top ) - ( paneRect . height / 2 ) + ( elemRect . height / 2 ) ;
4839+ previewPane . scrollTop = scrollTop ;
4840+ }
4841+ }
4842+
46904843 function syncHighlightScroll ( ) {
46914844 if ( ! editorHighlightLayer ) return ;
46924845 editorHighlightLayer . scrollTop = cachedScrollTop ;
0 commit comments