mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-16 12:39:31 +02:00
feat: tools by category
This commit is contained in:
44
.idea/workspace.xml
generated
44
.idea/workspace.xml
generated
@@ -4,25 +4,15 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: create transparent png">
|
||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: shortDescription">
|
||||
<change afterPath="$PROJECT_DIR$/src/assets/tools.png" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/components/Hero.tsx" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/pages/tools-by-category/index.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/scripts/create-tool.mjs" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/create-tool.mjs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/ToolInputAndResult.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/ToolInputAndResult.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/ToolLayout.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/ToolLayout.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/allTools/ToolCard.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/allTools/ToolCard.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/examples/ExampleCard.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/examples/ExampleCard.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/examples/Examples.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/examples/Examples.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/input/ToolTextInput.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/input/ToolTextInput.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/config/routesConfig.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/config/routesConfig.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/home/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/home/index.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/image/png/change-colors-in-png/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/image/png/change-colors-in-png/meta.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/image/png/create-transparent/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/image/png/create-transparent/meta.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/number/sum/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/number/sum/meta.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/string/join/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/string/join/index.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/string/join/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/string/join/meta.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/string/split/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/string/split/meta.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/string/to-morse/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/string/to-morse/meta.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/tools/defineTool.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/tools/defineTool.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/tools/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/tools/index.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/utils/string.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/utils/string.ts" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -202,14 +192,6 @@
|
||||
<workItem from="1719273044735" duration="9847000" />
|
||||
<workItem from="1719294110005" duration="3842000" />
|
||||
</task>
|
||||
<task id="LOCAL-00009" summary="chore: split string tools ui">
|
||||
<option name="closed" value="true" />
|
||||
<created>1718996769014</created>
|
||||
<option name="number" value="00009" />
|
||||
<option name="presentableId" value="LOCAL-00009" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1718996769014</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00010" summary="chore: use formik">
|
||||
<option name="closed" value="true" />
|
||||
<created>1718997842506</created>
|
||||
@@ -594,7 +576,15 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1719297880629</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="58" />
|
||||
<task id="LOCAL-00058" summary="feat: shortDescription">
|
||||
<option name="closed" value="true" />
|
||||
<created>1719301172510</created>
|
||||
<option name="number" value="00058" />
|
||||
<option name="presentableId" value="LOCAL-00058" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1719301172510</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="59" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -615,7 +605,6 @@
|
||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||
<option name="CHECK_NEW_TODO" value="false" />
|
||||
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
||||
<MESSAGE value="feat: copy and import file" />
|
||||
<MESSAGE value="refactor: tool options components" />
|
||||
<MESSAGE value="fix: join text service" />
|
||||
<MESSAGE value="test: join service" />
|
||||
@@ -640,7 +629,8 @@
|
||||
<MESSAGE value="fix: readme" />
|
||||
<MESSAGE value="refactor: tool input and result" />
|
||||
<MESSAGE value="feat: create transparent png" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="feat: create transparent png" />
|
||||
<MESSAGE value="feat: shortDescription" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="feat: shortDescription" />
|
||||
</component>
|
||||
<component name="XSLT-Support.FileAssociations.UIState">
|
||||
<expand />
|
||||
|
BIN
src/assets/tools.png
Normal file
BIN
src/assets/tools.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
100
src/components/Hero.tsx
Normal file
100
src/components/Hero.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Autocomplete, Box, Stack, TextField } from '@mui/material';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import { useState } from 'react';
|
||||
import { DefinedTool } from '@tools/defineTool';
|
||||
import { filterTools, tools } from '@tools/index';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const exampleTools: { label: string; url: string }[] = [
|
||||
{
|
||||
label: 'Create a transparent image',
|
||||
url: '/png/create-transparent'
|
||||
},
|
||||
{ label: 'Convert text to morse code', url: '/string/to-morse' },
|
||||
{ label: 'Change GIF speed', url: '' },
|
||||
{ label: 'Pick a random item', url: '' },
|
||||
{ label: 'Find and replace text', url: '' },
|
||||
{ label: 'Convert emoji to image', url: '' },
|
||||
{ label: 'Split a string', url: '/string/split' },
|
||||
{ label: 'Calculate number sum', url: '/number/sum' },
|
||||
{ label: 'Pixelate an image', url: '' }
|
||||
];
|
||||
export default function Hero() {
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const [filteredTools, setFilteredTools] = useState<DefinedTool[]>(tools);
|
||||
const navigate = useNavigate();
|
||||
const handleInputChange = (
|
||||
event: React.ChangeEvent<{}>,
|
||||
newInputValue: string
|
||||
) => {
|
||||
setInputValue(newInputValue);
|
||||
setFilteredTools(filterTools(tools, newInputValue));
|
||||
};
|
||||
return (
|
||||
<Box width={'60%'}>
|
||||
<Stack mb={1} direction={'row'} spacing={1}>
|
||||
<Typography fontSize={30}>Transform Your Workflow with </Typography>
|
||||
<Typography fontSize={30} color={'primary'}>
|
||||
Omni Tools
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography fontSize={20} mb={2}>
|
||||
Boost your productivity with Omni Tools, the ultimate toolkit for
|
||||
getting things done quickly! Access thousands of user-friendly utilities
|
||||
for editing images, text, lists, and data, all directly from your
|
||||
browser.
|
||||
</Typography>
|
||||
|
||||
<Autocomplete
|
||||
sx={{ mb: 2 }}
|
||||
autoHighlight
|
||||
options={filteredTools}
|
||||
getOptionLabel={(option) => option.name}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
fullWidth
|
||||
placeholder={'Search all tools'}
|
||||
sx={{ borderRadius: 2 }}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: <SearchIcon />
|
||||
}}
|
||||
onChange={(event) => handleInputChange(event, event.target.value)}
|
||||
/>
|
||||
)}
|
||||
renderOption={(props, option) => (
|
||||
<Box component="li" {...props} onClick={() => navigate(option.path)}>
|
||||
<Box>
|
||||
<Typography fontWeight={'bold'}>{option.name}</Typography>
|
||||
<Typography fontSize={12}>{option.shortDescription}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
/>
|
||||
<Grid container spacing={2} mt={2}>
|
||||
{exampleTools.map((tool) => (
|
||||
<Grid onClick={() => navigate(tool.url)} item xs={4} key={tool.label}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
padding: 1,
|
||||
borderRadius: 3,
|
||||
borderColor: 'grey',
|
||||
borderStyle: 'solid',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<Typography>{tool.label}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -5,6 +5,7 @@ import ToolHeader from './ToolHeader';
|
||||
import Separator from '@tools/Separator';
|
||||
import AllTools from './allTools/AllTools';
|
||||
import { getToolsByCategory } from '@tools/index';
|
||||
import { capitalizeFirstLetter } from '../utils/string';
|
||||
|
||||
export default function ToolLayout({
|
||||
children,
|
||||
@@ -43,7 +44,10 @@ export default function ToolLayout({
|
||||
<ToolHeader title={title} description={description} image={image} />
|
||||
{children}
|
||||
<Separator backgroundColor="#5581b5" margin="50px" />
|
||||
<AllTools title="All Text Tools" toolCards={otherCategoryTools} />
|
||||
<AllTools
|
||||
title={`All ${capitalizeFirstLetter(type)} tools`}
|
||||
toolCards={otherCategoryTools}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
@@ -3,12 +3,17 @@ import { Navigate } from 'react-router-dom';
|
||||
import { lazy } from 'react';
|
||||
|
||||
const Home = lazy(() => import('../pages/home'));
|
||||
const ToolsByCategory = lazy(() => import('../pages/tools-by-category'));
|
||||
|
||||
const routes: RouteObject[] = [
|
||||
{
|
||||
path: '/',
|
||||
element: <Home />
|
||||
},
|
||||
{
|
||||
path: '/categories/:categoryName',
|
||||
element: <ToolsByCategory />
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
element: <Navigate to="404" />
|
||||
|
@@ -1,45 +1,14 @@
|
||||
import {
|
||||
Autocomplete,
|
||||
Box,
|
||||
Card,
|
||||
CardContent,
|
||||
Stack,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import { Box, Card, CardContent, Stack } from '@mui/material';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { filterTools, getToolsByCategory, tools } from '../../tools';
|
||||
import { useState } from 'react';
|
||||
import { DefinedTool } from '@tools/defineTool';
|
||||
import { getToolsByCategory } from '../../tools';
|
||||
import Button from '@mui/material/Button';
|
||||
import Hero from 'components/Hero';
|
||||
|
||||
const exampleTools: { label: string; url: string }[] = [
|
||||
{
|
||||
label: 'Create a transparent image',
|
||||
url: '/png/create-transparent'
|
||||
},
|
||||
{ label: 'Convert text to morse code', url: '/string/to-morse' },
|
||||
{ label: 'Change GIF speed', url: '' },
|
||||
{ label: 'Pick a random item', url: '' },
|
||||
{ label: 'Find and replace text', url: '' },
|
||||
{ label: 'Convert emoji to image', url: '' },
|
||||
{ label: 'Split a string', url: '/string/split' },
|
||||
{ label: 'Calculate number sum', url: '/number/sum' },
|
||||
{ label: 'Pixelate an image', url: '' }
|
||||
];
|
||||
export default function Home() {
|
||||
const navigate = useNavigate();
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const [filteredTools, setFilteredTools] = useState<DefinedTool[]>(tools);
|
||||
const handleInputChange = (
|
||||
event: React.ChangeEvent<{}>,
|
||||
newInputValue: string
|
||||
) => {
|
||||
setInputValue(newInputValue);
|
||||
setFilteredTools(filterTools(tools, newInputValue));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
padding={5}
|
||||
@@ -49,108 +18,38 @@ export default function Home() {
|
||||
justifyContent={'center'}
|
||||
width={'100%'}
|
||||
>
|
||||
<Box width={'60%'}>
|
||||
<Stack mb={1} direction={'row'} spacing={1}>
|
||||
<Typography fontSize={30}>Transform Your Workflow with </Typography>
|
||||
<Typography fontSize={30} color={'primary'}>
|
||||
Omni Tools
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography fontSize={20} mb={2}>
|
||||
Boost your productivity with Omni Tools, the ultimate toolkit for
|
||||
getting things done quickly! Access thousands of user-friendly
|
||||
utilities for editing images, text, lists, and data, all directly from
|
||||
your browser.
|
||||
</Typography>
|
||||
|
||||
<Autocomplete
|
||||
sx={{ mb: 2 }}
|
||||
autoHighlight
|
||||
options={filteredTools}
|
||||
getOptionLabel={(option) => option.name}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
fullWidth
|
||||
placeholder={'Search all tools'}
|
||||
sx={{ borderRadius: 2 }}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: <SearchIcon />
|
||||
}}
|
||||
onChange={(event) => handleInputChange(event, event.target.value)}
|
||||
/>
|
||||
)}
|
||||
renderOption={(props, option) => (
|
||||
<Box
|
||||
component="li"
|
||||
{...props}
|
||||
onClick={() => navigate(option.path)}
|
||||
>
|
||||
<Box>
|
||||
<Typography fontWeight={'bold'}>{option.name}</Typography>
|
||||
<Typography fontSize={12}>{option.shortDescription}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
/>
|
||||
<Grid container spacing={2} mt={2}>
|
||||
{exampleTools.map((tool) => (
|
||||
<Grid
|
||||
onClick={() => navigate(tool.url)}
|
||||
item
|
||||
xs={4}
|
||||
key={tool.label}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
padding: 1,
|
||||
borderRadius: 3,
|
||||
borderColor: 'grey',
|
||||
borderStyle: 'solid',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<Typography>{tool.label}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
<Grid container mt={2} spacing={2}>
|
||||
{getToolsByCategory().map((category) => (
|
||||
<Grid key={category.type} item xs={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Link
|
||||
style={{ fontSize: 20 }}
|
||||
to={'/categories/' + category.type}
|
||||
>
|
||||
{category.title}
|
||||
</Link>
|
||||
<Typography sx={{ mt: 2 }}>{category.description}</Typography>
|
||||
<Stack
|
||||
mt={2}
|
||||
direction={'row'}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
<Button
|
||||
variant={'contained'}
|
||||
>{`See all ${category.title}`}</Button>
|
||||
<Button
|
||||
onClick={() => navigate(category.example.path)}
|
||||
variant={'outlined'}
|
||||
>{`Try ${category.example.title}`}</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
<Hero />
|
||||
<Grid width={'80%'} container mt={2} spacing={2}>
|
||||
{getToolsByCategory().map((category) => (
|
||||
<Grid key={category.type} item xs={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Link
|
||||
style={{ fontSize: 20 }}
|
||||
to={'/categories/' + category.type}
|
||||
>
|
||||
{category.title}
|
||||
</Link>
|
||||
<Typography sx={{ mt: 2 }}>{category.description}</Typography>
|
||||
<Stack
|
||||
mt={2}
|
||||
direction={'row'}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
<Button
|
||||
onClick={() => navigate('/categories/' + category.type)}
|
||||
variant={'contained'}
|
||||
>{`See all ${category.title}`}</Button>
|
||||
<Button
|
||||
onClick={() => navigate(category.example.path)}
|
||||
variant={'outlined'}
|
||||
>{`Try ${category.example.title}`}</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
74
src/pages/tools-by-category/index.tsx
Normal file
74
src/pages/tools-by-category/index.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
CardContent,
|
||||
Divider,
|
||||
Stack,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom';
|
||||
import { getToolsByCategory, tools } from '../../tools';
|
||||
import Button from '@mui/material/Button';
|
||||
import Hero from 'components/Hero';
|
||||
import AllTools from '../../components/allTools/AllTools';
|
||||
import { capitalizeFirstLetter } from '../../utils/string';
|
||||
import toolsPng from '@assets/tools.png';
|
||||
|
||||
export default function Home() {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const { categoryName } = useParams();
|
||||
return (
|
||||
<Box>
|
||||
<Box
|
||||
padding={5}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
width={'100%'}
|
||||
>
|
||||
<Hero />
|
||||
</Box>
|
||||
<Divider sx={{ borderColor: theme.palette.primary.main }} />
|
||||
<Box width={'100%'} mt={3} ml={7} padding={3}>
|
||||
<Typography
|
||||
fontSize={22}
|
||||
color={theme.palette.primary.main}
|
||||
>{`All ${capitalizeFirstLetter(categoryName)} Tools`}</Typography>
|
||||
<Grid container spacing={2} mt={2}>
|
||||
{getToolsByCategory()
|
||||
.find(({ type }) => type === categoryName)
|
||||
?.tools?.map((tool) => (
|
||||
<Grid item xs={12} md={4} key={tool.path}>
|
||||
<Stack
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.background.default // Change this to your desired hover color
|
||||
}
|
||||
}}
|
||||
onClick={() => navigate('/' + tool.path)}
|
||||
direction={'row'}
|
||||
spacing={2}
|
||||
padding={2}
|
||||
border={1}
|
||||
borderRadius={2}
|
||||
>
|
||||
<img width={100} src={tool.image ?? toolsPng} />
|
||||
<Box>
|
||||
<Link to={'/' + tool.path}>{tool.name}</Link>
|
||||
<Typography sx={{ mt: 2 }}>
|
||||
{tool.shortDescription}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
export function capitalizeFirstLetter(string: string) {
|
||||
export function capitalizeFirstLetter(string: string | undefined) {
|
||||
if (!string) return '';
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
Reference in New Issue
Block a user