Skip to content

Commit 1d0292d

Browse files
committed
feat: update appearance of search panel in markup mode
1 parent bf1e8f6 commit 1d0292d

File tree

21 files changed

+616
-343
lines changed

21 files changed

+616
-343
lines changed

src/bundle/MarkdownEditorView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ MarkdownEditorView.displayName = 'MarkdownEditorView';
420420
interface MarkupSearchAnchorProps extends Pick<EditorSettingsProps, 'mode'> {}
421421

422422
const MarkupSearchAnchor: React.FC<MarkupSearchAnchorProps> = ({mode}) => (
423-
<>{mode === 'markup' && <div className="g-md-search-anchor"></div>}</>
423+
<div className={`g-md-search-${mode}-anchor`}></div>
424424
);
425425

426426
function Settings(props: EditorSettingsProps & {stickyToolbar: boolean}) {

src/i18n/search/en.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
{
22
"label_case-sensitive": "Case sensitive",
33
"label_whole-word": "Whole word",
4-
"title": "Search in code",
4+
"title": "Search and replace",
5+
"action_close": "Close",
56
"action_replace": "Replace",
67
"action_replace_all": "Replace all",
7-
"replace_placeholder": "Replacement text"
8+
"action_next": "Find next",
9+
"action_prev": "Find previous",
10+
"action_expand": "Expand the replacement form",
11+
"title_search": "Find",
12+
"title_replace": "Replace with",
13+
"search_counter": "{{current}} of {{total}}",
14+
"search_placeholder": "Text search"
815
}

src/i18n/search/ru.json

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
{
22
"label_case-sensitive": "С учетом регистра",
3-
"label_whole-word": "Слово целиком",
4-
"title": "Найти в коде",
3+
"label_whole-word": "Точное совпадение",
4+
"title": "Поиск и замена",
5+
"action_close": "Закрыть",
56
"action_replace": "Заменить",
67
"action_replace_all": "Заменить всё",
7-
"replace_placeholder": "Текст замены"
8+
"action_next": "Следующий",
9+
"action_prev": "Предыдущий",
10+
"action_expand": "Раскрыть окно замены",
11+
"title_search": "Найти",
12+
"title_replace": "Заменить на",
13+
"search_counter": "{{current}} из {{total}}",
14+
"search_placeholder": "Поиск по тексту"
815
}

src/markup/codemirror/create.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ export function createCodemirror(params: CreateCodemirrorParams) {
293293
if (searchPanel) {
294294
extensions.push(
295295
SearchPanelPlugin({
296-
anchorSelector: '.g-md-search-anchor',
296+
anchorSelector: '.g-md-search-markup-anchor',
297297
receiver,
298298
}),
299299
);

src/markup/codemirror/search-plugin/plugin.ts

Lines changed: 50 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,15 @@ import {
2222
import type {MarkdownEditorMode} from 'src/bundle';
2323
import type {EventMap} from 'src/bundle/Editor';
2424
import type {RendererItem} from 'src/extensions';
25-
import {debounce} from 'src/lodash';
25+
import {renderSearchPopup} from 'src/modules/search';
2626
import type {Receiver} from 'src/utils';
2727

2828
import {ReactRendererFacet} from '../react-facet';
2929

3030
import {searchTheme} from './theme';
31-
import {renderSearchPopup} from './view/SearchPopup';
3231

3332
type SearchQueryConfig = ConstructorParameters<typeof SearchQuery>[0];
3433

35-
const INPUT_DELAY = 200;
36-
3734
export interface SearchPanelPluginParams {
3835
anchorSelector: string;
3936
inputDelay?: number;
@@ -46,85 +43,70 @@ export const SearchPanelPlugin = (params: SearchPanelPluginParams) =>
4643
readonly view: EditorView;
4744
readonly params: SearchPanelPluginParams;
4845

49-
anchor: HTMLElement | null;
50-
renderer: RendererItem | null;
51-
searchConfig: SearchQueryConfig = {
52-
search: '',
53-
caseSensitive: false,
54-
wholeWord: false,
55-
replace: '',
56-
};
46+
renderer: RendererItem;
5747
receiver: Receiver<EventMap> | undefined;
5848

59-
setViewSearchWithDelay: (config: Partial<SearchQueryConfig>) => void;
49+
panelOpened: boolean;
50+
searchState: SearchQuery | null;
6051

6152
constructor(view: EditorView) {
6253
this.view = view;
63-
this.anchor = null;
64-
this.renderer = null;
6554
this.params = params;
6655
this.receiver = params.receiver;
6756

57+
this.panelOpened = searchPanelOpen(view.state);
58+
this.searchState = getSearchQuery(view.state);
59+
this.renderer = this.createRenderer();
60+
6861
this.handleClose = this.handleClose.bind(this);
6962
this.handleChange = this.handleChange.bind(this);
7063
this.handleSearchNext = this.handleSearchNext.bind(this);
7164
this.handleSearchPrev = this.handleSearchPrev.bind(this);
7265
this.handleReplaceNext = this.handleReplaceNext.bind(this);
7366
this.handleReplaceAll = this.handleReplaceAll.bind(this);
74-
this.handleSearchConfigChange = this.handleSearchConfigChange.bind(this);
7567
this.handleEditorModeChange = this.handleEditorModeChange.bind(this);
7668

77-
this.setViewSearchWithDelay = debounce(
78-
this.setViewSearch,
79-
this.params.inputDelay ?? INPUT_DELAY,
80-
);
8169
this.receiver?.on('change-editor-mode', this.handleEditorModeChange);
8270
}
8371

8472
update(update: ViewUpdate): void {
8573
const isPanelOpen = searchPanelOpen(update.state);
74+
const searchQuery = getSearchQuery(update.state);
8675

87-
if (isPanelOpen && !this.renderer) {
88-
const initial = getSearchQuery(update.state);
89-
this.anchor = document.querySelector(this.params.anchorSelector);
90-
this.renderer = this.view.state
91-
.facet(ReactRendererFacet)
92-
.createItem('cm-search', () =>
93-
renderSearchPopup({
94-
initial,
95-
open: true,
96-
anchor: this.anchor,
97-
onChange: this.handleChange,
98-
onClose: this.handleClose,
99-
onSearchNext: this.handleSearchNext,
100-
onSearchPrev: this.handleSearchPrev,
101-
onReplaceNext: this.handleReplaceNext,
102-
onReplaceAll: this.handleReplaceAll,
103-
onConfigChange: this.handleSearchConfigChange,
104-
}),
105-
);
106-
} else if (!isPanelOpen && this.renderer) {
107-
this.renderer?.remove();
108-
this.renderer = null;
76+
if (isPanelOpen !== this.panelOpened || searchQuery !== this.searchState) {
77+
this.panelOpened = isPanelOpen;
78+
this.searchState = searchQuery;
79+
this.renderer.rerender();
10980
}
11081
}
11182

11283
destroy() {
113-
this.renderer?.remove();
114-
this.renderer = null;
84+
this.renderer.remove();
11585
this.receiver?.off('change-editor-mode', this.handleEditorModeChange);
11686
}
11787

118-
setViewSearch(config: Partial<SearchQueryConfig>) {
119-
this.searchConfig = {
120-
...this.searchConfig,
121-
...config,
122-
};
123-
const searchQuery = new SearchQuery({
124-
...this.searchConfig,
88+
createRenderer() {
89+
return this.view.state.facet(ReactRendererFacet).createItem('cm-search', () => {
90+
if (!this.panelOpened || !this.searchState) return null;
91+
92+
const anchor = this.view.dom.ownerDocument.querySelector(
93+
this.params.anchorSelector,
94+
);
95+
96+
if (!anchor) return null;
97+
98+
return renderSearchPopup({
99+
open: true,
100+
anchor: anchor,
101+
state: this.searchState,
102+
onClose: this.handleClose,
103+
onChange: this.handleChange,
104+
onSearchNext: this.handleSearchNext,
105+
onSearchPrev: this.handleSearchPrev,
106+
onReplaceNext: this.handleReplaceNext,
107+
onReplaceAll: this.handleReplaceAll,
108+
});
125109
});
126-
127-
this.view.dispatch({effects: setSearchQuery.of(searchQuery)});
128110
}
129111

130112
handleEditorModeChange({mode}: {mode: MarkdownEditorMode}) {
@@ -133,13 +115,23 @@ export const SearchPanelPlugin = (params: SearchPanelPluginParams) =>
133115
}
134116
}
135117

136-
handleChange(search: string) {
137-
this.setViewSearchWithDelay({search});
118+
handleChange(config: SearchQueryConfig) {
119+
this.view.dispatch({
120+
effects: setSearchQuery.of(
121+
new SearchQuery({
122+
search: config.search,
123+
replace: config.replace,
124+
caseSensitive: config.caseSensitive,
125+
wholeWord: config.wholeWord,
126+
}),
127+
),
128+
});
138129
}
139130

140131
handleClose() {
141-
this.setViewSearch({search: ''});
132+
this.handleChange({search: ''});
142133
closeSearchPanel(this.view);
134+
this.view.focus();
143135
}
144136

145137
handleSearchNext() {
@@ -150,17 +142,11 @@ export const SearchPanelPlugin = (params: SearchPanelPluginParams) =>
150142
findPrevious(this.view);
151143
}
152144

153-
handleSearchConfigChange(config: Partial<SearchQueryConfig>) {
154-
this.setViewSearch(config);
155-
}
156-
157-
handleReplaceNext(query: string, replacement: string) {
158-
this.setViewSearch({search: query, replace: replacement});
145+
handleReplaceNext() {
159146
replaceNext(this.view);
160147
}
161148

162-
handleReplaceAll(query: string, replacement: string) {
163-
this.setViewSearch({search: query, replace: replacement});
149+
handleReplaceAll() {
164150
replaceAll(this.view);
165151
}
166152
},

src/markup/codemirror/search-plugin/view/ReplaceIcons.tsx

Lines changed: 0 additions & 35 deletions
This file was deleted.

src/markup/codemirror/search-plugin/view/SearchPopup.scss

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)