From 52ccfec60f1114b0e3ad39acd5a25530d9bf80b8 Mon Sep 17 00:00:00 2001 From: EugSh <127679450+EugSh1@users.noreply.github.com> Date: Thu, 6 Mar 2025 10:34:44 +0300 Subject: [PATCH 1/2] feat: minor improvements and refactoring in Text Replacer Tool - Changed the replaceText method to take all options as arguments. - Removed compute from SimpleRadio component. - Moved InitialValuesType type and initialValues object to a separate file to avoid Fast Refresh error. - Used ToolContent and added usage examples. --- src/pages/tools/string/index.ts | 4 +- .../tools/string/text-replacer/index.tsx | 146 +++++++++++++++ .../string/text-replacer/initialValues.ts | 15 ++ src/pages/tools/string/text-replacer/meta.ts | 13 ++ .../text-replacer/replaceText.service.test.ts | 175 ++++++++++++++++++ .../tools/string/text-replacer/service.ts | 40 ++++ 6 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 src/pages/tools/string/text-replacer/index.tsx create mode 100644 src/pages/tools/string/text-replacer/initialValues.ts create mode 100644 src/pages/tools/string/text-replacer/meta.ts create mode 100644 src/pages/tools/string/text-replacer/replaceText.service.test.ts create mode 100644 src/pages/tools/string/text-replacer/service.ts 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..be16cf3 --- /dev/null +++ b/src/pages/tools/string/text-replacer/index.tsx @@ -0,0 +1,146 @@ +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; + } +} From fe1250218b67db4481c00ec41b1a669b025d96ea Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Thu, 6 Mar 2025 17:41:47 +0000 Subject: [PATCH 2/2] chore: placeholder and empty tag --- .idea/workspace.xml | 163 +++++++++++------- .../tools/string/text-replacer/index.tsx | 7 +- 2 files changed, 102 insertions(+), 68 deletions(-) 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 @@