diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index e98c9e4..2e63911 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,12 +4,9 @@
-
+
-
-
-
-
+
@@ -25,7 +22,7 @@
@@ -41,6 +38,38 @@
"assignee": "iib0011"
}
}
+
{
"selectedUrlAndAccountId": {
"url": "https://github.com/iib0011/omni-tools.git",
@@ -65,51 +94,52 @@
- {
- "keyToString": {
- "ASKED_ADD_EXTERNAL_FILES": "true",
- "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
- "Docker.Dockerfile build.executor": "Run",
- "Docker.Dockerfile.executor": "Run",
- "Playwright.JoinText Component.executor": "Run",
- "Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run",
- "RunOnceActivity.OpenProjectViewOnStart": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "RunOnceActivity.git.unshallow": "true",
- "Vitest.compute function (1).executor": "Run",
- "Vitest.compute function.executor": "Run",
- "Vitest.mergeText.executor": "Run",
- "Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run",
- "Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run",
- "Vitest.removeDuplicateLines function.executor": "Run",
- "Vitest.removeDuplicateLines function.newlines option.executor": "Run",
- "Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run",
- "git-widget-placeholder": "main",
- "ignore.virus.scanning.warn.message": "true",
- "kotlin-language-version-configured": "true",
- "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/public/assets",
- "node.js.detected.package.eslint": "true",
- "node.js.detected.package.tslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "npm.build.executor": "Run",
- "npm.dev.executor": "Run",
- "npm.lint.executor": "Run",
- "npm.prebuild.executor": "Run",
- "npm.script:create:tool.executor": "Run",
- "npm.test.executor": "Run",
- "npm.test:e2e.executor": "Run",
- "npm.test:e2e:run.executor": "Run",
- "prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier",
- "project.structure.last.edited": "Problems",
- "project.structure.proportion": "0.0",
- "project.structure.side.proportion": "0.2",
- "settings.editor.selected.configurable": "settings.typescriptcompiler",
- "ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
- "vue.rearranger.settings.migration": "true"
- }
- }
+
@@ -770,8 +803,8 @@
-
-
+
+
diff --git a/src/pages/tools/string/index.ts b/src/pages/tools/string/index.ts
index b9e5b0f..ee02eba 100644
--- a/src/pages/tools/string/index.ts
+++ b/src/pages/tools/string/index.ts
@@ -8,12 +8,14 @@ import { tool as stringPalindrome } from './palindrome/meta';
import { tool as stringToMorse } from './to-morse/meta';
import { tool as stringSplit } from './split/meta';
import { tool as stringJoin } from './join/meta';
+import { tool as stringReplace } from './text-replacer/meta';
export const stringTools = [
stringSplit,
stringJoin,
stringRemoveDuplicateLines,
- stringToMorse
+ stringToMorse,
+ stringReplace
// stringReverse,
// stringRandomizeCase,
// stringUppercase,
diff --git a/src/pages/tools/string/text-replacer/index.tsx b/src/pages/tools/string/text-replacer/index.tsx
new file mode 100644
index 0000000..ee99fe0
--- /dev/null
+++ b/src/pages/tools/string/text-replacer/index.tsx
@@ -0,0 +1,147 @@
+import { Box } from '@mui/material';
+import { useState } from 'react';
+import ToolTextResult from '@components/result/ToolTextResult';
+import { GetGroupsType } from '@components/options/ToolOptions';
+import { replaceText } from './service';
+import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
+import ToolTextInput from '@components/input/ToolTextInput';
+import SimpleRadio from '@components/options/SimpleRadio';
+import { initialValues, InitialValuesType } from './initialValues';
+import ToolContent from '@components/ToolContent';
+import { CardExampleType } from '@components/examples/ToolExamples';
+import { ToolComponentProps } from '@tools/defineTool';
+
+const exampleCards: CardExampleType[] = [
+ {
+ title: 'Replace specific word in text',
+ description:
+ 'In this example we will replace the word "hello" with the word "hi". This example doesn\'t use regular expressions.',
+ sampleText: 'hello, how are you today? hello!',
+ sampleResult: 'hi, how are you today? hi!',
+ sampleOptions: {
+ textToReplace: 'hello, how are you today? hello!',
+ searchValue: 'hello',
+ searchRegexp: '',
+ replaceValue: 'hi',
+ mode: 'text'
+ }
+ },
+ {
+ title: 'Replace all numbers in text',
+ description:
+ 'In this example we will replace all numbers in numbers with * using regexp. In the output we will get text with numbers replaced with *.',
+ sampleText: 'The price is 100$, and the discount is 20%.',
+ sampleResult: 'The price is X$, and the discount is X%.',
+ sampleOptions: {
+ textToReplace: 'The price is 100$, and the discount is 20%.',
+ searchValue: '',
+ searchRegexp: '/\\d+/g',
+ replaceValue: '*',
+ mode: 'regexp'
+ }
+ },
+ {
+ title: 'Replace all dates in text',
+ description:
+ 'In this example we will replace all dates in the format YYYY-MM-DD with the word DATE using regexp. The output will have all the dates replaced with the word DATE.',
+ sampleText:
+ 'The event will take place on 2025-03-10, and the deadline is 2025-03-15.',
+ sampleResult:
+ 'The event will take place on DATE, and the deadline is DATE.',
+ sampleOptions: {
+ textToReplace:
+ 'The event will take place on 2025-03-10, and the deadline is 2025-03-15.',
+ searchValue: '',
+ searchRegexp: '/\\d{4}-\\d{2}-\\d{2}/g',
+ replaceValue: 'DATE',
+ mode: 'regexp'
+ }
+ }
+];
+
+export default function Replacer({ title }: ToolComponentProps) {
+ const [input, setInput] = useState('');
+ const [result, setResult] = useState('');
+
+ function compute(optionsValues: InitialValuesType, input: string) {
+ setResult(replaceText(optionsValues, input));
+ }
+
+ const getGroups: GetGroupsType = ({
+ values,
+ updateField
+ }) => [
+ {
+ title: 'Search text',
+ component: (
+
+ updateField('mode', 'text')}
+ checked={values.mode === 'text'}
+ title={'Find This Pattern in Text'}
+ />
+ updateField('searchValue', val)}
+ type={'text'}
+ />
+ updateField('mode', 'regexp')}
+ checked={values.mode === 'regexp'}
+ title={'Find a Pattern Using a RegExp'}
+ />
+ updateField('searchRegexp', val)}
+ type={'text'}
+ />
+
+ )
+ },
+ {
+ title: 'Replace Text',
+ component: (
+
+ updateField('replaceValue', val)}
+ type={'text'}
+ />
+
+ )
+ }
+ ];
+
+ return (
+
+ }
+ resultComponent={
+
+ }
+ toolInfo={{
+ title: 'Text Replacer',
+ description:
+ 'Easily replace specific text in your content with this simple, browser-based tool. Just input your text, set the text you want to replace and the replacement value, and instantly get the updated version.'
+ }}
+ exampleCards={exampleCards}
+ />
+ );
+}
diff --git a/src/pages/tools/string/text-replacer/initialValues.ts b/src/pages/tools/string/text-replacer/initialValues.ts
new file mode 100644
index 0000000..9c52e84
--- /dev/null
+++ b/src/pages/tools/string/text-replacer/initialValues.ts
@@ -0,0 +1,15 @@
+export type InitialValuesType = {
+ textToReplace: string;
+ searchValue: string;
+ searchRegexp: string;
+ replaceValue: string;
+ mode: 'text' | 'regexp';
+};
+
+export const initialValues: InitialValuesType = {
+ textToReplace: '',
+ searchValue: '',
+ searchRegexp: '',
+ replaceValue: '',
+ mode: 'text'
+};
diff --git a/src/pages/tools/string/text-replacer/meta.ts b/src/pages/tools/string/text-replacer/meta.ts
new file mode 100644
index 0000000..a7162f7
--- /dev/null
+++ b/src/pages/tools/string/text-replacer/meta.ts
@@ -0,0 +1,13 @@
+import { defineTool } from '@tools/defineTool';
+import { lazy } from 'react';
+
+export const tool = defineTool('string', {
+ name: 'Text Replacer',
+ path: 'replacer',
+ shortDescription: 'Quickly replace text in your content',
+ icon: 'material-symbols-light:find-replace',
+ description:
+ 'Easily replace specific text in your content with this simple, browser-based tool. Just input your text, set the text you want to replace and the replacement value, and instantly get the updated version.',
+ keywords: ['text', 'replace'],
+ component: lazy(() => import('./index'))
+});
diff --git a/src/pages/tools/string/text-replacer/replaceText.service.test.ts b/src/pages/tools/string/text-replacer/replaceText.service.test.ts
new file mode 100644
index 0000000..5df2aae
--- /dev/null
+++ b/src/pages/tools/string/text-replacer/replaceText.service.test.ts
@@ -0,0 +1,175 @@
+import { describe, expect, it } from 'vitest';
+import { replaceText } from './service';
+import { initialValues } from './initialValues';
+
+describe('replaceText function (text mode)', () => {
+ const mode = 'text';
+
+ it('should replace the word in the text correctly', () => {
+ const text = 'Lorem ipsum odor amet, consectetuer adipiscing elit.';
+ const searchValue = 'ipsum';
+ const replaceValue = 'vitae';
+ const result = replaceText(
+ { ...initialValues, searchValue, replaceValue, mode },
+ text
+ );
+ expect(result).toBe('Lorem vitae odor amet, consectetuer adipiscing elit.');
+ });
+
+ it('should replace letters in the text correctly', () => {
+ const text =
+ 'Luctus penatibus montes elementum lacus mus vivamus lacus laoreet.';
+ const searchValue = 'e';
+ const replaceValue = 'u';
+ const result = replaceText(
+ { ...initialValues, searchValue, replaceValue, mode },
+ text
+ );
+ expect(result).toBe(
+ 'Luctus punatibus montus ulumuntum lacus mus vivamus lacus laoruut.'
+ );
+ });
+
+ it('should return the original text if one of the required arguments is an empty string', () => {
+ const text =
+ 'Nostra netus quisque ornare neque dolor sem nostra venenatis.';
+ expect(
+ replaceText(
+ { ...initialValues, searchValue: '', replaceValue: 'test', mode },
+ text
+ )
+ ).toBe('Nostra netus quisque ornare neque dolor sem nostra venenatis.');
+ expect(
+ replaceText(
+ { ...initialValues, searchValue: 'ornare', replaceValue: 'test', mode },
+ ''
+ )
+ ).toBe('');
+ });
+
+ it('should replace multiple occurrences of the word correctly', () => {
+ const text = 'apple orange apple banana apple';
+ const searchValue = 'apple';
+ const replaceValue = 'grape';
+ const result = replaceText(
+ { ...initialValues, searchValue, replaceValue, mode },
+ text
+ );
+ expect(result).toBe('grape orange grape banana grape');
+ });
+
+ it('should return the original text if the replace value is an empty string', () => {
+ const text = 'apple orange apple banana apple';
+ const searchValue = 'apple';
+ const replaceValue = '';
+ const result = replaceText(
+ { ...initialValues, searchValue, replaceValue, mode },
+ text
+ );
+ expect(result).toBe(' orange banana ');
+ });
+
+ it('should return the original text if the search value is not found', () => {
+ const text = 'apple orange banana';
+ const searchValue = 'grape';
+ const replaceValue = 'melon';
+ const result = replaceText(
+ { ...initialValues, searchValue, replaceValue, mode },
+ text
+ );
+ expect(result).toBe('apple orange banana');
+ });
+});
+
+describe('replaceText function (regexp mode)', () => {
+ const mode = 'regexp';
+
+ it('should replace a word in text using regexp correctly', () => {
+ const text = 'Egestas lobortis facilisi convallis rhoncus nunc.';
+ const searchRegexp = '/nunc/';
+ const replaceValue = 'hello';
+ const result = replaceText(
+ { ...initialValues, searchRegexp, replaceValue, mode },
+ text
+ );
+ expect(result).toBe('Egestas lobortis facilisi convallis rhoncus hello.');
+ });
+
+ it('should replace all words in the text with regexp correctly', () => {
+ const text =
+ 'Parturient porta ultricies tellus ultricies suscipit quisque torquent.';
+ const searchRegexp = '/ultricies/g';
+ const replaceValue = 'hello';
+ const result = replaceText(
+ { ...initialValues, searchRegexp, replaceValue, mode },
+ text
+ );
+ expect(result).toBe(
+ 'Parturient porta hello tellus hello suscipit quisque torquent.'
+ );
+ });
+
+ it('should replace words in text with regexp using alternation operator correctly', () => {
+ const text =
+ 'Commodo maximus nullam dis placerat fermentum curabitur semper.';
+ const searchRegexp = '/nullam|fermentum/g';
+ const replaceValue = 'test';
+ const result = replaceText(
+ { ...initialValues, searchRegexp, replaceValue, mode },
+ text
+ );
+ expect(result).toBe(
+ 'Commodo maximus test dis placerat test curabitur semper.'
+ );
+ });
+
+ it('should return the original text when passed an invalid regexp', () => {
+ const text =
+ 'Commodo maximus nullam dis placerat fermentum curabitur semper.';
+ const searchRegexp = '/(/';
+ const replaceValue = 'test';
+ const result = replaceText(
+ { ...initialValues, searchRegexp, replaceValue, mode },
+ text
+ );
+ expect(result).toBe(
+ 'Commodo maximus nullam dis placerat fermentum curabitur semper.'
+ );
+ });
+
+ it('should remove brackets from text correctly using regexp', () => {
+ const text =
+ 'Porta nulla (magna) lectus, [taciti] habitant nunc urna maximus metus.';
+ const searchRegexp = '/[()\\[\\]]/g';
+ const replaceValue = '';
+ const result = replaceText(
+ { ...initialValues, searchRegexp, replaceValue, mode },
+ text
+ );
+ expect(result).toBe(
+ 'Porta nulla magna lectus, taciti habitant nunc urna maximus metus.'
+ );
+ });
+
+ it('should replace case-insensitive words correctly', () => {
+ const text = 'Porta cras ad laoreet porttitor feRmeNtum consectetur?';
+ const searchRegexp = '/porta|fermentum/gi';
+ const replaceValue = 'test';
+ const result = replaceText(
+ { ...initialValues, searchRegexp, replaceValue, mode },
+ text
+ );
+ expect(result).toBe('test cras ad laoreet porttitor test consectetur?');
+ });
+
+ it('should replace words with digits and symbols correctly', () => {
+ const text = 'The price is 100$, and the discount is 20%.';
+ const searchRegexp = '/\\d+/g';
+ const replaceValue = 'X';
+ const result = replaceText(
+ { ...initialValues, searchRegexp, replaceValue, mode },
+ text
+ );
+ expect(result).toBe('The price is X$, and the discount is X%.');
+ });
+});
diff --git a/src/pages/tools/string/text-replacer/service.ts b/src/pages/tools/string/text-replacer/service.ts
new file mode 100644
index 0000000..e633b12
--- /dev/null
+++ b/src/pages/tools/string/text-replacer/service.ts
@@ -0,0 +1,40 @@
+import { InitialValuesType } from './initialValues';
+
+function isFieldsEmpty(textField: string, searchField: string) {
+ return !textField.trim() || !searchField.trim();
+}
+
+export function replaceText(options: InitialValuesType, text: string) {
+ const { searchValue, searchRegexp, replaceValue, mode } = options;
+
+ switch (mode) {
+ case 'text':
+ if (isFieldsEmpty(text, searchValue)) return text;
+ return text.replaceAll(searchValue, replaceValue);
+ case 'regexp':
+ if (isFieldsEmpty(text, searchRegexp)) return text;
+ return replaceTextWithRegexp(text, searchRegexp, replaceValue);
+ }
+}
+
+function replaceTextWithRegexp(
+ text: string,
+ searchRegexp: string,
+ replaceValue: string
+) {
+ try {
+ const match = searchRegexp.match(/^\/(.*)\/([a-z]*)$/i);
+
+ if (match) {
+ // Input is in /pattern/flags format
+ const [, pattern, flags] = match;
+ return text.replace(new RegExp(pattern, flags), replaceValue);
+ } else {
+ // Input is a raw pattern - don't escape it
+ return text.replace(new RegExp(searchRegexp, 'g'), replaceValue);
+ }
+ } catch (err) {
+ console.error('Invalid regular expression:', err);
+ return text;
+ }
+}