Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/i18n/search/en.json
Original file line number Diff line number Diff line change
@@ -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"
}
7 changes: 5 additions & 2 deletions src/i18n/search/ru.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"label_case-sensitive": "С учетом регистра",
"label_whole-word": "Слово целиком",
"title": "Найти в коде"
}
"title": "Найти в коде",
"action_replace": "Заменить",
"action_replace_all": "Заменить всё",
"replace_placeholder": "Текст замены"
}
17 changes: 17 additions & 0 deletions src/markup/codemirror/search-plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
findNext,
findPrevious,
getSearchQuery,
replaceAll,
replaceNext,
search,
searchKeymap,
searchPanelOpen,
Expand Down Expand Up @@ -48,6 +50,7 @@ export const SearchPanelPlugin = (params: SearchPanelPluginParams) =>
search: '',
caseSensitive: false,
wholeWord: false,
replace: '',
};
receiver: Receiver<EventMap> | undefined;

Expand All @@ -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);

Expand Down Expand Up @@ -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,
}),
);
Expand Down Expand Up @@ -144,6 +151,16 @@ export const SearchPanelPlugin = (params: SearchPanelPluginParams) =>
handleSearchConfigChange(config: Partial<SearchQueryConfig>) {
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: () => [
Expand Down
35 changes: 35 additions & 0 deletions src/markup/codemirror/search-plugin/view/ReplaceIcons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type React from 'react';

export const ReplaceIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
<svg
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={14}
height={14}
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.221 3.739l2.261 2.269L7.7 3.784l-.7-.7-1.012 1.007-.008-1.6a.523.523 0 0 1 .5-.526H8V1H6.48A1.482 1.482 0 0 0 5 2.489V4.1L3.927 3.033l-.706.706zm6.67 1.794h.01c.183.311.451.467.806.467.393 0 .706-.168.94-.503.236-.335.353-.78.353-1.333 0-.511-.1-.913-.301-1.207-.201-.295-.488-.442-.86-.442-.405 0-.718.194-.938.581h-.01V1H9v4.919h.89v-.386zm-.015-1.061v-.34c0-.248.058-.448.175-.601a.54.54 0 0 1 .445-.23.49.49 0 0 1 .436.233c.104.154.155.368.155.643 0 .33-.056.587-.169.768a.524.524 0 0 1-.47.27.495.495 0 0 1-.411-.211.853.853 0 0 1-.16-.532zM9 12.769c-.256.154-.625.231-1.108.231-.563 0-1.02-.178-1.369-.533-.349-.355-.523-.813-.523-1.374 0-.648.186-1.158.56-1.53.374-.376.875-.563 1.5-.563.433 0 .746.06.94.179v.998a1.26 1.26 0 0 0-.792-.276c-.325 0-.583.1-.774.298-.19.196-.283.468-.283.816 0 .338.09.603.272.797.182.191.431.287.749.287.282 0 .558-.092.828-.276v.946zM4 7L3 8v6l1 1h7l1-1V8l-1-1H4zm0 1h7v6H4V8z"
/>
</svg>
);

export const ReplaceAllIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
<svg
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width={14}
height={14}
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.6 2.677c.147-.31.356-.465.626-.465.248 0 .44.118.573.353.134.236.201.557.201.966 0 .443-.078.798-.235 1.067-.156.268-.365.402-.627.402-.237 0-.416-.125-.537-.374h-.008v.31H11V1h.593v1.677h.008zm-.016 1.1a.78.78 0 0 0 .107.426c.071.113.163.169.274.169.136 0 .24-.072.314-.216.075-.145.113-.35.113-.615 0-.22-.035-.39-.104-.514-.067-.124-.164-.187-.29-.187-.12 0-.219.062-.297.185a.886.886 0 0 0-.117.48v.272zM4.12 7.695L2 5.568l.662-.662 1.006 1v-1.51A1.39 1.39 0 0 1 5.055 3H7.4v.905H5.055a.49.49 0 0 0-.468.493l.007 1.5.949-.944.656.656-2.08 2.085zM9.356 4.93H10V3.22C10 2.408 9.685 2 9.056 2c-.135 0-.285.024-.45.073a1.444 1.444 0 0 0-.388.167v.665c.237-.203.487-.304.75-.304.261 0 .392.156.392.469l-.6.103c-.506.086-.76.406-.76.961 0 .263.061.473.183.631A.61.61 0 0 0 8.69 5c.29 0 .509-.16.657-.48h.009v.41zm.004-1.355v.193a.75.75 0 0 1-.12.436.368.368 0 0 1-.313.17.276.276 0 0 1-.22-.095.38.38 0 0 1-.08-.248c0-.222.11-.351.332-.389l.4-.067zM7 12.93h-.644v-.41h-.009c-.148.32-.367.48-.657.48a.61.61 0 0 1-.507-.235c-.122-.158-.183-.368-.183-.63 0-.556.254-.876.76-.962l.6-.103c0-.313-.13-.47-.392-.47-.263 0-.513.102-.75.305v-.665c.095-.063.224-.119.388-.167.165-.049.315-.073.45-.073.63 0 .944.407.944 1.22v1.71zm-.64-1.162v-.193l-.4.068c-.222.037-.333.166-.333.388 0 .1.027.183.08.248a.276.276 0 0 0 .22.095.368.368 0 0 0 .312-.17c.08-.116.12-.26.12-.436zM9.262 13c.321 0 .568-.058.738-.173v-.71a.9.9 0 0 1-.552.207.619.619 0 0 1-.5-.215c-.12-.145-.181-.345-.181-.598 0-.26.063-.464.189-.612a.644.644 0 0 1 .516-.223c.194 0 .37.069.528.207v-.749c-.129-.09-.338-.134-.626-.134-.417 0-.751.14-1.001.422-.249.28-.373.662-.373 1.148 0 .42.116.764.349 1.03.232.267.537.4.913.4zM2 9l1-1h9l1 1v5l-1 1H3l-1-1V9zm1 0v5h9V9H3zm3-2l1-1h7l1 1v5l-1 1V7H6z"
/>
</svg>
);
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
50 changes: 48 additions & 2 deletions src/markup/codemirror/search-plugin/view/SearchPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<SearchQuery, 'search' | 'caseSensitive' | 'wholeWord'>;
Expand All @@ -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;
}

Expand All @@ -43,11 +47,14 @@ export const SearchCard: React.FC<SearchCardProps> = ({
onClose = noop,
onSearchPrev = noop,
onSearchNext = noop,
onReplaceNext = noop,
onReplaceAll = noop,
onConfigChange = noop,
}) => {
const [query, setQuery] = useState<string>(initial.search);
const [isCaseSensitive, setIsCaseSensitive] = useState<boolean>(initial.caseSensitive);
const [isWholeWord, setIsWholeWord] = useState<boolean>(initial.wholeWord);
const [replacement, setReplacement] = useState<string>('');
const textInputRef = useRef<HTMLInputElement>(null);

const setInputFocus = () => {
Expand Down Expand Up @@ -75,6 +82,16 @@ export const SearchCard: React.FC<SearchCardProps> = ({
setInputFocus();
};

const handleReplace = () => {
onReplaceNext(query, replacement);
setInputFocus();
};

const handleReplaceAll = () => {
onReplaceAll(query, replacement);
setInputFocus();
};

const handleIsCaseSensitive = () => {
onConfigChange({
caseSensitive: !isCaseSensitive,
Expand Down Expand Up @@ -113,15 +130,44 @@ export const SearchCard: React.FC<SearchCardProps> = ({
value={query}
endContent={
<>
<Button onClick={handlePrev}>
<Button onClick={handlePrev} pin="round-brick">
<Icon data={ChevronUp} size={12} />
</Button>
<Button onClick={handleNext}>
<Button onClick={handleNext} pin="brick-round">
<Icon data={ChevronDown} size={12} />
</Button>
</>
}
/>
<TextInput
placeholder={i18n('replace_placeholder')}
className={sp({mb: 2})}
size="s"
onUpdate={setReplacement}
value={replacement}
endContent={
<>
<Button
size="s"
onClick={handleReplace}
pin="round-brick"
disabled={!query}
title={i18n('action_replace')}
>
<ReplaceIcon width={12} height={12} />
</Button>
<Button
size="s"
onClick={handleReplaceAll}
pin="brick-round"
disabled={!query}
title={i18n('action_replace_all')}
>
<ReplaceAllIcon width={12} height={12} />
</Button>
</>
}
/>
<Checkbox
size="m"
onUpdate={handleIsCaseSensitive}
Expand Down
Loading