1- import {
2- SearchQuery ,
3- findNext ,
4- findPrev ,
5- getMatchHighlights ,
6- getSearchState ,
7- replaceAll ,
8- replaceNext ,
9- setSearchState ,
10- } from 'prosemirror-search' ;
11-
12- import { keydownHandler } from '#pm/keymap' ;
13- import {
14- type Command ,
15- type EditorState ,
16- Plugin ,
17- PluginKey ,
18- type PluginView ,
19- type Transaction ,
20- } from '#pm/state' ;
1+ import { SearchQuery , getSearchState , setSearchState } from 'prosemirror-search' ;
2+
3+ import { type Command , Plugin , type PluginView } from '#pm/state' ;
214import type { EditorView } from '#pm/view' ;
225import { debounce } from 'src/lodash' ;
236import { type SearchCounter , type SearchPopupProps , renderSearchPopup } from 'src/search' ;
247
258import { getReactRendererFromState } from '../ReactRenderer' ;
26- import { hideSelectionMenu } from '../SelectionContext' ;
279
28- import { FocusManager } from './FocusManager' ;
10+ import { closeSearch , findNext , findPrev , replaceAll , replaceNext } from './commands' ;
11+ import { pluginKey } from './const' ;
12+ import { searchKeyHandler } from './key-handler' ;
13+ import type { SearchViewState } from './types' ;
14+ import { startTracking } from './utils/connect-tracker' ;
15+ import { FocusManager } from './utils/focus-manager' ;
16+ import { getCounter } from './utils/search-counter' ;
2917
3018import './search-plugin.scss' ;
3119
32- const pluginKey = new PluginKey < SearchViewState > ( 'search-view' ) ;
33-
34- const SearchClassName = {
35- Match : 'ProseMirror-search-match' ,
36- ActiveMatch : 'ProseMirror-active-search-match' ,
37- } ;
38-
39- type TrMeta = { open : false } | { open : true ; search : string } ;
4020type SearchQueryConfig = ConstructorParameters < typeof SearchQuery > [ 0 ] ;
4121
42- const openSearch : Command = ( state , dispatch ) => {
43- const pluginState = pluginKey . getState ( state ) ;
44- if ( ! pluginState ?. open && dispatch ) {
45- const search = state . doc . textBetween ( state . selection . from , state . selection . to , ' ' ) ;
46- const meta : TrMeta = { open : true , search} ;
47- dispatch (
48- setSearchState (
49- hideSelectionMenu ( state . tr . setMeta ( pluginKey , meta ) ) ,
50- new SearchQuery ( { search} ) ,
51- ) ,
52- ) ;
53- }
54- return true ;
55- } ;
56-
57- const hideSearch : Command = ( state , dispatch ) => {
58- if ( dispatch ) {
59- const meta : TrMeta = { open : false } ;
60- dispatch ( setSearchState ( state . tr . setMeta ( pluginKey , meta ) , new SearchQuery ( { search : '' } ) ) ) ;
61- }
62- return true ;
63- } ;
64-
65- type SearchState = {
66- query : string ;
67- replacement ?: string ;
68- } ;
69-
70- type SearchViewState =
71- | {
72- open : false ;
73- }
74- | {
75- open : true ;
76- state : SearchState ;
77- } ;
78-
7922export interface SearchViewPluginParams {
8023 anchorSelector : string ;
8124}
@@ -84,14 +27,12 @@ export const searchViewPlugin = (params: SearchViewPluginParams) => {
8427 return new Plugin < SearchViewState > ( {
8528 key : pluginKey ,
8629 props : {
87- handleKeyDown : keydownHandler ( {
88- 'Mod-f' : openSearch ,
89- } ) ,
30+ handleKeyDown : searchKeyHandler ,
9031 } ,
9132 state : {
9233 init : ( ) => ( { open : false } ) ,
9334 apply ( tr , value , _oldState , _newState ) {
94- const newValue = tr . getMeta ( pluginKey ) ;
35+ const newValue = tr . getMeta ( pluginKey ) as SearchViewState | undefined ;
9536 if ( typeof newValue === 'object' ) return newValue ;
9637 return value ;
9738 } ,
@@ -106,8 +47,10 @@ class SeachPluginView implements PluginView {
10647 private readonly _view : EditorView ;
10748 private readonly _renderer ;
10849 private readonly _focusManager : FocusManager ;
50+ private readonly _viewDomTrackerDispose ;
10951
11052 private _counter : SearchCounter ;
53+ private _isDomConnected : boolean ;
11154 private _viewState : SearchViewState | undefined ;
11255 private _searchState : ReturnType < typeof getSearchState > ;
11356
@@ -119,9 +62,14 @@ class SeachPluginView implements PluginView {
11962 this . _viewState = pluginKey . getState ( view . state ) ;
12063 this . _searchState = getSearchState ( view . state ) ;
12164 this . _counter = getCounter ( view . state ) ;
65+ this . _isDomConnected = view . dom . ownerDocument . contains ( view . dom ) ;
12266
12367 this . _renderer = this . _createRenderer ( params ) ;
12468 this . _focusManager = new FocusManager ( view . dom . ownerDocument ) ;
69+ this . _viewDomTrackerDispose = startTracking ( view . dom , {
70+ onConnect : this . _onEditorViewDomConnected ,
71+ onDisconnect : this . _onEditorViewDomDisconnected ,
72+ } ) ;
12573
12674 this . _onSearchChangeDebounced = debounce ( this . _handleSearchChange . bind ( this ) ) ;
12775 this . _onReplacementChangeDebounced = debounce ( this . _handleReplacementChange . bind ( this ) ) ;
@@ -130,8 +78,6 @@ class SeachPluginView implements PluginView {
13078 update ( ) {
13179 const counter = getCounter ( this . _view . state ) ;
13280 const newViewState = pluginKey . getState ( this . _view . state ) ;
133- console . log ( 'searchViewPlugin view update' , { newViewState} ) ;
134- // const newSearchState = getSearchState(view.state);
13581 if (
13682 newViewState !== this . _viewState ||
13783 counter . total !== this . _counter . total ||
@@ -140,10 +86,6 @@ class SeachPluginView implements PluginView {
14086 this . _counter = counter ;
14187 this . _viewState = newViewState ;
14288 this . _searchState = getSearchState ( this . _view . state ) ;
143- console . log ( 'searchViewPlugin view update rerender' , {
144- viewState : this . _viewState ,
145- searchState : this . _searchState ,
146- } ) ;
14789 this . _renderer . rerender ( ) ;
14890 }
14991 }
@@ -152,20 +94,30 @@ class SeachPluginView implements PluginView {
15294 this . _onSearchChangeDebounced . cancel ( ) ;
15395 this . _onReplacementChangeDebounced . cancel ( ) ;
15496 this . _renderer . remove ( ) ;
97+ this . _viewDomTrackerDispose ( ) ;
15598 }
15699
100+ private _onEditorViewDomConnected = ( ) => {
101+ this . _isDomConnected = true ;
102+ this . _renderer . rerender ( ) ;
103+ } ;
104+
105+ private _onEditorViewDomDisconnected = ( ) => {
106+ this . _isDomConnected = false ;
107+ this . _onClose ( ) ;
108+ } ;
109+
157110 private _createRenderer ( params : Pick < SearchViewPluginParams , 'anchorSelector' > ) {
158111 return getReactRendererFromState ( this . _view . state ) . createItem ( 'search-view' , ( ) => {
159- const { _viewState : viewState , _searchState : searchState } = this ;
112+ const {
113+ _viewState : viewState ,
114+ _searchState : searchState ,
115+ _isDomConnected : domConnected ,
116+ } = this ;
160117
161- console . log ( 'searchViewPlugin render' , { viewState, searchState} ) ;
162- if ( ! viewState ?. open || ! searchState ) return null ;
118+ if ( ! domConnected || ! viewState ?. open || ! searchState ) return null ;
163119
164120 const anchor = this . _view . dom . ownerDocument . querySelector ( params . anchorSelector ) ;
165- console . log ( 'searchViewPlugin render anchor' , {
166- anchor,
167- anchorSelector : params . anchorSelector ,
168- } ) ;
169121
170122 return renderSearchPopup ( {
171123 anchor,
@@ -207,8 +159,6 @@ class SeachPluginView implements PluginView {
207159 } ;
208160 }
209161
210- console . log ( 'searchViewPlugin updateSearchState' , { prevSearchState, prevConfig, config} ) ;
211-
212162 dispatch (
213163 setSearchState (
214164 state . tr ,
@@ -223,34 +173,33 @@ class SeachPluginView implements PluginView {
223173 private _onClose = ( ) => {
224174 this . _onSearchChangeDebounced . cancel ( ) ;
225175 this . _onReplacementChangeDebounced . cancel ( ) ;
226- hideSearch ( this . _view . state , this . _view . dispatch ) ;
176+ closeSearch ( this . _view . state , this . _view . dispatch ) ;
227177 this . _view . focus ( ) ;
228178 } ;
229179
230180 private _onSearchPrev = ( ) => {
231181 this . _onSearchChangeDebounced . flush ( ) ;
232- this . _preserveFocus ( wrapCommand ( findPrev , hideSelectionMenu ) ) ;
182+ this . _preserveFocus ( findPrev ) ;
233183 } ;
234184
235185 private _onSearchNext = ( ) => {
236186 this . _onSearchChangeDebounced . flush ( ) ;
237- this . _preserveFocus ( wrapCommand ( findNext , hideSelectionMenu ) ) ;
187+ this . _preserveFocus ( findNext ) ;
238188 } ;
239189
240190 private _onReplaceNext = ( ) => {
241191 this . _onSearchChangeDebounced . flush ( ) ;
242192 this . _onReplacementChangeDebounced . flush ( ) ;
243- this . _preserveFocus ( wrapCommand ( replaceNext , hideSelectionMenu ) ) ;
193+ this . _preserveFocus ( replaceNext ) ;
244194 } ;
245195
246196 private _onReplaceAll = ( ) => {
247197 this . _onSearchChangeDebounced . flush ( ) ;
248198 this . _onReplacementChangeDebounced . flush ( ) ;
249- this . _preserveFocus ( wrapCommand ( replaceAll , hideSelectionMenu ) ) ;
199+ this . _preserveFocus ( replaceAll ) ;
250200 } ;
251201
252202 private _onConfigChange : SearchPopupProps [ 'onConfigChange' ] = ( config ) => {
253- console . log ( 'searchViewPlugin onConfigChange' , config ) ;
254203 this . _updateSearchState ( {
255204 wholeWord : config . wholeWord ,
256205 caseSensitive : config . caseSensitive ,
@@ -264,31 +213,3 @@ class SeachPluginView implements PluginView {
264213 this . _focusManager . restoreFocus ( { preventScroll : true } ) ;
265214 }
266215}
267-
268- function wrapCommand ( command : Command , wrapper : ( tr : Transaction ) => void ) : Command {
269- return ( state , dispatch , view ) => {
270- return command (
271- state ,
272- dispatch
273- ? ( tr ) => {
274- wrapper ( tr ) ;
275- return dispatch ( tr ) ;
276- }
277- : undefined ,
278- view ,
279- ) ;
280- } ;
281- }
282-
283- function getCounter ( state : EditorState ) : SearchCounter {
284- const decoSet = getMatchHighlights ( state ) ;
285- const searchDecos = decoSet . find ( undefined , undefined , ( ) => true ) ;
286- const activeIndex = searchDecos . findIndex ( ( deco ) => {
287- const d = deco as { type ?: { attrs ?: Record < string , any > } } ;
288- return d . type ?. attrs ?. class === SearchClassName . ActiveMatch ;
289- } ) ;
290- return {
291- total : searchDecos . length ,
292- current : activeIndex + 1 ,
293- } ;
294- }
0 commit comments