diff --git a/src/i18n/search/en.json b/src/i18n/search/en.json index 670d3b3e1..3d03107b0 100644 --- a/src/i18n/search/en.json +++ b/src/i18n/search/en.json @@ -1,5 +1,8 @@ { "label_case-sensitive": "Case sensitive", "label_whole-word": "Whole word", - "title": "Search in code" -} + "title": "Search in code", + "action_replace": "Replace", + "action_replace_all": "Replace all", + "replace_placeholder": "Replacement text" +} \ No newline at end of file diff --git a/src/i18n/search/ru.json b/src/i18n/search/ru.json index 6e19ad55a..2d4a5d001 100644 --- a/src/i18n/search/ru.json +++ b/src/i18n/search/ru.json @@ -1,5 +1,8 @@ { "label_case-sensitive": "С учетом регистра", "label_whole-word": "Слово целиком", - "title": "Найти в коде" -} + "title": "Найти в коде", + "action_replace": "Заменить", + "action_replace_all": "Заменить всё", + "replace_placeholder": "Текст замены" +} \ No newline at end of file diff --git a/src/markup/codemirror/search-plugin/plugin.ts b/src/markup/codemirror/search-plugin/plugin.ts index 114743955..86dd5b775 100644 --- a/src/markup/codemirror/search-plugin/plugin.ts +++ b/src/markup/codemirror/search-plugin/plugin.ts @@ -4,6 +4,8 @@ import { findNext, findPrevious, getSearchQuery, + replaceAll, + replaceNext, search, searchKeymap, searchPanelOpen, @@ -48,6 +50,7 @@ export const SearchPanelPlugin = (params: SearchPanelPluginParams) => search: '', caseSensitive: false, wholeWord: false, + replace: '', }; receiver: Receiver | undefined; @@ -64,6 +67,8 @@ export const SearchPanelPlugin = (params: SearchPanelPluginParams) => this.handleChange = this.handleChange.bind(this); this.handleSearchNext = this.handleSearchNext.bind(this); this.handleSearchPrev = this.handleSearchPrev.bind(this); + this.handleReplaceNext = this.handleReplaceNext.bind(this); + this.handleReplaceAll = this.handleReplaceAll.bind(this); this.handleSearchConfigChange = this.handleSearchConfigChange.bind(this); this.handleEditorModeChange = this.handleEditorModeChange.bind(this); @@ -91,6 +96,8 @@ export const SearchPanelPlugin = (params: SearchPanelPluginParams) => onClose: this.handleClose, onSearchNext: this.handleSearchNext, onSearchPrev: this.handleSearchPrev, + onReplaceNext: this.handleReplaceNext, + onReplaceAll: this.handleReplaceAll, onConfigChange: this.handleSearchConfigChange, }), ); @@ -144,6 +151,16 @@ export const SearchPanelPlugin = (params: SearchPanelPluginParams) => handleSearchConfigChange(config: Partial) { this.setViewSearch(config); } + + handleReplaceNext(query: string, replacement: string) { + this.setViewSearch({search: query, replace: replacement}); + replaceNext(this.view); + } + + handleReplaceAll(query: string, replacement: string) { + this.setViewSearch({search: query, replace: replacement}); + replaceAll(this.view); + } }, { provide: () => [ diff --git a/src/markup/codemirror/search-plugin/view/ReplaceIcons.tsx b/src/markup/codemirror/search-plugin/view/ReplaceIcons.tsx new file mode 100644 index 000000000..de8202838 --- /dev/null +++ b/src/markup/codemirror/search-plugin/view/ReplaceIcons.tsx @@ -0,0 +1,35 @@ +import type React from 'react'; + +export const ReplaceIcon: React.FC> = (props) => ( + + + +); + +export const ReplaceAllIcon: React.FC> = (props) => ( + + + +); diff --git a/src/markup/codemirror/search-plugin/view/SearchPopup.scss b/src/markup/codemirror/search-plugin/view/SearchPopup.scss index 7a7a30dd2..743728f39 100644 --- a/src/markup/codemirror/search-plugin/view/SearchPopup.scss +++ b/src/markup/codemirror/search-plugin/view/SearchPopup.scss @@ -1,4 +1,5 @@ .g-md-search-card { + width: 450px; padding: var(--g-spacing-2) var(--g-spacing-2) var(--g-spacing-3) var(--g-spacing-4); &__header { diff --git a/src/markup/codemirror/search-plugin/view/SearchPopup.tsx b/src/markup/codemirror/search-plugin/view/SearchPopup.tsx index 5b9fa81d8..a58fd131e 100644 --- a/src/markup/codemirror/search-plugin/view/SearchPopup.tsx +++ b/src/markup/codemirror/search-plugin/view/SearchPopup.tsx @@ -17,6 +17,8 @@ import {cn} from '../../../../classname'; import {i18n} from '../../../../i18n/search'; import {enterKeyHandler} from '../../../../utils/handlers'; +import {ReplaceAllIcon, ReplaceIcon} from './ReplaceIcons'; + import './SearchPopup.scss'; type SearchInitial = Pick; @@ -29,6 +31,8 @@ interface SearchCardProps { onClose?: (query: string) => void; onSearchPrev?: (query: string) => void; onSearchNext?: (query: string) => void; + onReplaceNext?: (query: string, replacement: string) => void; + onReplaceAll?: (query: string, replacement: string) => void; onConfigChange?: (config: SearchConfig) => void; } @@ -43,11 +47,14 @@ export const SearchCard: React.FC = ({ onClose = noop, onSearchPrev = noop, onSearchNext = noop, + onReplaceNext = noop, + onReplaceAll = noop, onConfigChange = noop, }) => { const [query, setQuery] = useState(initial.search); const [isCaseSensitive, setIsCaseSensitive] = useState(initial.caseSensitive); const [isWholeWord, setIsWholeWord] = useState(initial.wholeWord); + const [replacement, setReplacement] = useState(''); const textInputRef = useRef(null); const setInputFocus = () => { @@ -75,6 +82,16 @@ export const SearchCard: React.FC = ({ setInputFocus(); }; + const handleReplace = () => { + onReplaceNext(query, replacement); + setInputFocus(); + }; + + const handleReplaceAll = () => { + onReplaceAll(query, replacement); + setInputFocus(); + }; + const handleIsCaseSensitive = () => { onConfigChange({ caseSensitive: !isCaseSensitive, @@ -113,15 +130,44 @@ export const SearchCard: React.FC = ({ value={query} endContent={ <> - - } /> + + + + + } + />