diff --git a/biome.json b/biome.json
index e1a8ab6..ee424a4 100644
--- a/biome.json
+++ b/biome.json
@@ -2,17 +2,55 @@
"$schema": "https://biomejs.dev/schemas/1.6.3/schema.json",
"formatter": {
"enabled": true,
- "formatWithErrors": false,
+ "formatWithErrors": true,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 80,
- "attributePosition": "auto"
+ "attributePosition": "auto",
+ "ignore": [
+ "**/node_modules/**",
+ "**/dist/**",
+ "**/build/**",
+ "**/coverage/**",
+ "**/.git/**",
+ "**/.vscode/**",
+ "**/.github/**",
+ "**/.yarn/**",
+ "**/.pnp/**",
+ "**/.cache/**",
+ "**/.next/**",
+ "**/.nuxt/**",
+ "**/out/**",
+ "**/public/**",
+ "**/static"
+ ]
},
"organizeImports": { "enabled": true },
- "linter": { "enabled": true, "rules": { "recommended": true } },
+ "linter": {
+ "enabled": true,
+ "rules": { "recommended": true },
+ "ignore": [
+ "**/node_modules/**",
+ "**/dist/**",
+ "**/build/**",
+ "**/coverage/**",
+ "**/.git/**",
+ "**/.vscode/**",
+ "**/.github/**",
+ "**/.yarn/**",
+ "**/.pnp/**",
+ "**/.cache/**",
+ "**/.next/**",
+ "**/.nuxt/**",
+ "**/out/**",
+ "**/public/**",
+ "**/static"
+ ]
+ },
"javascript": {
"formatter": {
+ "enabled": true,
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingComma": "es5",
diff --git a/components/AliasedEmails.tsx b/components/AliasedEmails.tsx
index 0b40b89..1f9a6df 100644
--- a/components/AliasedEmails.tsx
+++ b/components/AliasedEmails.tsx
@@ -1,4 +1,4 @@
-import { AliasType } from "@/components/types"
+import type { AliasType } from "@/components/types"
import { colorSelector } from "@/utils/colorSelector"
import { faker } from "@faker-js/faker"
import {
@@ -26,9 +26,8 @@ import {
IconX,
} from "@tabler/icons-react"
import aliasedEmail from "aliased-email"
-import { useAtom } from "jotai"
import { useCallback, useEffect, useState } from "react"
-import { localCopyHistoryAtom } from "./global/CopyHistory"
+import { CopyComponent } from "@/components/global/CopyComponent"
type Props = { extension?: boolean }
@@ -43,7 +42,6 @@ export default function InputCreator({ extension }: Props) {
defaultValue: "",
})
const [finalEmail, setFinalEmail] = useState("")
- const [copiedEmail, setCopiedEmail] = useState("")
const [timestampEnabled, setTimestampEnabled] = useLocalStorage({
key: "timestampEnabled",
defaultValue: true,
@@ -65,12 +63,12 @@ export default function InputCreator({ extension }: Props) {
}
const handleCreateAlias = (query: string) => {
- query = query.trim().replaceAll(/\W/g, "")
- if (aliases.find((a) => a.value === query)) return
- if (query.length === 0) return
+ const trimmedQuery = query.trim().replaceAll(/\W/g, "")
+ if (aliases.find((a) => a.value === trimmedQuery)) return
+ if (trimmedQuery.length === 0) return
setAliases([
...aliases,
- { label: query, value: query.trim().replaceAll(/\W/g, "") },
+ { label: trimmedQuery, value: trimmedQuery.trim().replaceAll(/\W/g, "") },
])
}
const [editingAliases, setEditingAliases] = useState(false)
@@ -84,24 +82,6 @@ export default function InputCreator({ extension }: Props) {
}
}
- const [copyHistory, setCopyHistory] = useAtom(localCopyHistoryAtom)
- const uniqueCopyHistory = new Set(copyHistory.map((item) => item.value))
-
- const handleCopyEmail = () => {
- setCopiedEmail(finalEmail)
-
- if (uniqueCopyHistory.has(finalEmail)) return
- setCopyHistory((history) => [
- ...history,
- {
- id: history.length,
- type: "email",
- value: finalEmail,
- timestamp: new Date().getTime(),
- },
- ])
- }
-
const useRealtimeTimestamp = () => {
const [realtimeTimestamp, setRealtimeTimestamp] = useState("")
@@ -206,7 +186,7 @@ export default function InputCreator({ extension }: Props) {
Random Alias
-
+ {}
- >
- )}
-
+
+ }
+ disabled={!validateEmail(email) || email.length === 0}
+ />
diff --git a/components/RegexReplacer.tsx b/components/RegexReplacer.tsx
index 016284a..1e4a52f 100644
--- a/components/RegexReplacer.tsx
+++ b/components/RegexReplacer.tsx
@@ -1,50 +1,130 @@
-import { Box, Button, CopyButton, Input, Text } from "@mantine/core"
+import { Box, Card, Grid, Input, Text, Tooltip } from "@mantine/core"
import { useInputState } from "@mantine/hooks"
import React, { useEffect, useState } from "react"
import { regexReplacer } from "@/utils/regexReplacer"
+import { stringToRegex } from "@/utils/transformStringToRegex"
+import { CopyComponent } from "@/components/global/CopyComponent"
+import { IconAlertCircle, IconRegex } from "@tabler/icons-react"
-type Props = {}
+type Props = {
+ extension?: boolean
+}
-const RegexReplacer = (props: Props) => {
+const RegexReplacer = ({ extension }: Props) => {
const [regexInput, setRegexInput] = useInputState("")
const [replaceValue, setReplaceValue] = useInputState("")
const [testText, setTestText] = useInputState("")
const [preview, setPreview] = useInputState("")
-
- //convert the regexInput to regex
+ const [regex, setRegex] = useState(null)
useEffect(() => {
+ setRegex(stringToRegex(regexInput))
setPreview(regexReplacer(regexInput, replaceValue, testText))
- }, [regexInput, replaceValue, testText])
+ }, [regexInput, replaceValue, testText, setPreview])
return (
-
-
-
-
-
-
-
-
-
-
-
-
- {({ copied, copy }) => {preview}}
-
-
+
+
+
+ Regex Replace Tester
+
+
+
+ }
+ radius={"xl"}
+ autoComplete="off"
+ autoSave="off"
+ data-test="regex-input"
+ rightSection={
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0 && regex.toString() !== "/(?:)/"
+ ? false
+ : true
+ }
+ data-test="regex-copy"
+ />
+
+
+ 0 ? false : true}
+ data-test="regex-preview"
+ />
+
+
+
+
+
+
)
}
diff --git a/components/TextGeneration.tsx b/components/TextGeneration.tsx
index 7738509..3c4b81d 100644
--- a/components/TextGeneration.tsx
+++ b/components/TextGeneration.tsx
@@ -15,6 +15,7 @@ import { PlaceText } from "../utils/transformer"
import { useAtom } from "jotai"
import { localCopyHistoryAtom } from "./global/CopyHistory"
import { colorSelector } from "@/utils/colorSelector"
+import { CopyComponent } from "@/components/global/CopyComponent"
interface Props {
extension?: boolean
@@ -32,16 +33,18 @@ export default function CopyGroupCard({ defaultOptions }: Props) {
(acc, option) => {
const key = option.textElement
const copyButton = (
-
)
if (acc[key]) {
@@ -101,60 +104,3 @@ export default function CopyGroupCard({ defaultOptions }: Props) {
)
}
-
-export const CopyButtons = ({
- label,
- textElement,
- text: value,
- ...rest
-}: {
- label: string
- textElement: string
- text: string
-}) => {
- const [copyHistory, setCopyHistory] = useAtom(localCopyHistoryAtom)
-
- return (
-
- {({ copied, copy }) => (
-
- {
- copy()
- if (
- !copyHistory ||
- copyHistory[0] === undefined ||
- copyHistory[0]["value"] !== value
- ) {
- setCopyHistory((history) => [
- ...history,
- {
- id: history.length,
- type: textElement,
- value: value,
- timestamp: Date.now(),
- },
- ])
- }
- }}
- >
- {copied ? `Copied ${label}` : label}
-
-
- )}
-
- )
-}
diff --git a/components/global/ConfirmationPopup.tsx b/components/global/ConfirmationPopup.tsx
index 866a0da..8eb6400 100644
--- a/components/global/ConfirmationPopup.tsx
+++ b/components/global/ConfirmationPopup.tsx
@@ -2,7 +2,7 @@ import {
Badge,
Button,
Group,
- MantineColor,
+ type MantineColor,
Popover,
Stack,
Text,
@@ -20,7 +20,7 @@ type Props = {
onCancel?: () => void
confirmColor?: MantineColor
cancelColor?: MantineColor
- [x: string]: any
+ [x: string]: string | boolean | (() => void) | undefined
}
export default function ConfirmationPopup({
title,
@@ -38,9 +38,7 @@ export default function ConfirmationPopup({
const [opened, setOpened] = useState(false)
const handleCancel = () => {
- {
- onCancel && onCancel()
- }
+ onCancel?.()
setOpened(false)
}
diff --git a/components/global/CopyComponent.tsx b/components/global/CopyComponent.tsx
new file mode 100644
index 0000000..f2e0467
--- /dev/null
+++ b/components/global/CopyComponent.tsx
@@ -0,0 +1,73 @@
+import { localCopyHistoryAtom } from "@/components/global/CopyHistory"
+import type { CopyHistoryType } from "@/components/types"
+import { Box, Button, CopyButton, Tooltip } from "@mantine/core"
+import { useAtom } from "jotai"
+import { colorSelector } from "../../utils/colorSelector"
+import { useState } from "react"
+
+type Props = {
+ value?: string
+ type: string
+ label?: string
+ fullWidth?: boolean
+ size?: "xs" | "sm" | "md" | "lg" | "xl"
+ id?: string
+ h?: number | string
+ maw?: number | string
+ rightIcon?: React.ReactNode
+ disabled?: boolean
+}
+
+export const CopyComponent = ({ value, type, label, ...rest }: Props) => {
+ const [copyHistory, setCopyHistory] = useAtom(localCopyHistoryAtom)
+
+ const [displayedCopiedValue, setDisplayedCopiedValue] = useState("")
+
+ const handleCopy = (value: string, type: string) => {
+ setDisplayedCopiedValue(value)
+
+ if (copyHistory[copyHistory.length - 1]?.value === value) {
+ return
+ }
+ setCopyHistory((history) => [
+ ...history,
+ {
+ id: history.length,
+ type: type,
+ value,
+ timestamp: new Date().getTime(),
+ },
+ ])
+ }
+ return (
+
+
+ {({ copied, copy }) => (
+
+ {
+ copy()
+ handleCopy(value ? value : "", type)
+ }}
+ >
+ {copied ? `Copied ${displayedCopiedValue}` : label}
+
+
+ )}
+
+
+ )
+}
diff --git a/components/global/CopyHistory.tsx b/components/global/CopyHistory.tsx
index ba7c9e4..e14c480 100644
--- a/components/global/CopyHistory.tsx
+++ b/components/global/CopyHistory.tsx
@@ -7,7 +7,7 @@ import {
Button,
CopyButton,
Flex,
- MantineNumberSize,
+ type MantineNumberSize,
SimpleGrid,
Text,
Tooltip,
@@ -15,10 +15,10 @@ import {
import { IconCopy } from "@tabler/icons-react"
import { colorSelector } from "@/utils/colorSelector"
import { useRouter } from "next/router"
-import { CopyHistoryType } from "@/components/types"
+import type { CopyHistoryType } from "@/components/types"
type Props = {
- type?: "email" | "text"
+ type?: "email" | "text" | "regex" | "string"
spacing?: MantineNumberSize
tooltip?: boolean
scrollThreshold: number
@@ -30,24 +30,18 @@ export const localCopyHistoryAtom = atomWithStorage(
)
export default function CopyHistory({ type, tooltip, scrollThreshold }: Props) {
- let copyHistory = useAtomValue(localCopyHistoryAtom)
+ const copyHistory = useAtomValue(localCopyHistoryAtom)
const router = useRouter()
const extension = router.pathname.includes("/extension")
- type == "email" &&
- (copyHistory = copyHistory.filter((item) => item.type === type))
-
- type == "text" &&
- (copyHistory = copyHistory.filter((item) => item.type === type))
-
const ScrollingText = ({
children,
scrollThreshold,
scrolling,
...rest
}: {
- children: String
+ children: string
scrollThreshold: number
scrolling: boolean
}) => {
@@ -59,17 +53,15 @@ export default function CopyHistory({ type, tooltip, scrollThreshold }: Props) {
const speed = scrollThreshold * 0.02
if (speed > 0.8) {
return 0.8
- } else {
- return speed
}
+ return speed
}
const scrollAmount = (scrollThreshold: number) => {
if (children.length <= scrollThreshold) {
return 0
- } else {
- return (scrollThreshold - children.length) * scalingFactor + "ch"
}
+ return `${(scrollThreshold - children.length) * scalingFactor}ch`
}
return (
@@ -91,7 +83,7 @@ export default function CopyHistory({ type, tooltip, scrollThreshold }: Props) {
}
: {
transform: `translateX(${scrollAmount(scrollThreshold)})`,
- transition: `transform 0.1s ease-out`,
+ transition: "transform 0.1s ease-out",
}
}
>
@@ -108,10 +100,10 @@ export default function CopyHistory({ type, tooltip, scrollThreshold }: Props) {
historyItem: CopyHistoryType
}) => {
return (
-
+
{({ copied, copy }) => (
}
color={colorSelector(historyItem.type)}
variant={copied ? "filled" : "outline"}
@@ -157,19 +149,18 @@ export default function CopyHistory({ type, tooltip, scrollThreshold }: Props) {
month: "numeric",
day: "numeric",
})
- } else {
- return new Date(date).toLocaleDateString("en-US", {
- year: "numeric",
- month: "numeric",
- day: "numeric",
- })
}
+ return new Date(date).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "numeric",
+ day: "numeric",
+ })
}
const groupedData: { [key: string]: CopyHistoryType[] } = copyHistory.reduce(
(acc: { [key: string]: CopyHistoryType[] }, item) => {
let itemTimestamp = item.timestamp
- if (itemTimestamp == undefined || itemTimestamp == null) {
+ if (itemTimestamp === undefined || itemTimestamp == null) {
itemTimestamp = new Date(946800000000).getTime()
}
const date = parseDate(new Date(itemTimestamp))
@@ -194,7 +185,7 @@ export default function CopyHistory({ type, tooltip, scrollThreshold }: Props) {
setHistory((history) =>
history.filter((item) => {
let itemTimestamp = item.timestamp
- if (itemTimestamp == undefined || itemTimestamp == null) {
+ if (itemTimestamp === undefined || itemTimestamp == null) {
itemTimestamp = new Date(946800000000).getTime()
}
parseDate(new Date(itemTimestamp)) !== parseDate(new Date(date))
@@ -208,7 +199,7 @@ export default function CopyHistory({ type, tooltip, scrollThreshold }: Props) {
.sort((a, b) => new Date(b).getTime() - new Date(a).getTime())
.map((dates) => {
return (
-
+
{!deleting ? (
<>
@@ -227,7 +218,7 @@ export default function CopyHistory({ type, tooltip, scrollThreshold }: Props) {
{dates === parseDate(new Date())
- ? "Today, " + parseDate(new Date(dates), false)
+ ? `Today, ${parseDate(new Date(dates), false)}`
: parseDate(new Date(dates), true)}
>
@@ -238,7 +229,7 @@ export default function CopyHistory({ type, tooltip, scrollThreshold }: Props) {
fullWidth
color="red"
onClick={() => {
- handleDeleteHistoryGroup(parseInt(dates))
+ handleDeleteHistoryGroup(Number.parseInt(dates))
}}
>
Clear
@@ -246,16 +237,34 @@ export default function CopyHistory({ type, tooltip, scrollThreshold }: Props) {
)}
-
+
{groupedData[dates]
.sort()
.reverse()
- .map((historyItem: CopyHistoryType) => (
-
- ))}
+ .map((historyItem: CopyHistoryType) => {
+ // exclude all but the last non-duplicate item
+ if (
+ historyItem.id !==
+ groupedData[dates].find(
+ (item) =>
+ item.type === historyItem.type &&
+ item.value === historyItem.value
+ )?.id
+ ) {
+ return null
+ }
+
+ return (
+
+ )
+ })}
)
diff --git a/components/global/SelectScroll.tsx b/components/global/SelectScroll.tsx
index 5f2b43a..0d079fa 100644
--- a/components/global/SelectScroll.tsx
+++ b/components/global/SelectScroll.tsx
@@ -4,7 +4,6 @@
// lines that almost look like the top of a mouse wheel
import { Box, Select } from "@mantine/core"
-import React, { useState } from "react"
interface Props {
options: string[]
diff --git a/components/global/logo.tsx b/components/global/logo.tsx
index e55f8f5..befdca7 100644
--- a/components/global/logo.tsx
+++ b/components/global/logo.tsx
@@ -1,3 +1,4 @@
+import { Image } from "@mantine/core"
import React from "react"
export default function Logo({ ...rest }) {
@@ -6,6 +7,8 @@ export default function Logo({ ...rest }) {
viewBox="-20 18 90 80"
width={"60px"}
xmlns="http://www.w3.org/2000/svg"
+ aria-label="logo"
+ role="img"
{...rest}
>
diff --git a/components/types.ts b/components/types.ts
index e0f63e3..3037146 100644
--- a/components/types.ts
+++ b/components/types.ts
@@ -6,7 +6,7 @@ export type AliasType = {
export type CopyHistoryType = {
id: number
type: string
- value: string
+ value: string | RegExp | null
timestamp?: number
}
diff --git a/package-lock.json b/package-lock.json
index b5f4ede..010fd97 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,9 +11,9 @@
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/server": "^11.10.0",
- "@mantine/core": "^6.0.8",
- "@mantine/hooks": "^6.0.8",
- "@mantine/next": "^6.0.8",
+ "@mantine/core": "^6.0.21",
+ "@mantine/hooks": "^6.0.21",
+ "@mantine/next": "^6.0.21",
"@tabler/icons-react": "2.17.0",
"@types/node": "18.16.0",
"@types/react": "18.0.38",
@@ -1016,20 +1016,20 @@
}
},
"node_modules/@floating-ui/core": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.4.1.tgz",
- "integrity": "sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
+ "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==",
"dependencies": {
- "@floating-ui/utils": "^0.1.1"
+ "@floating-ui/utils": "^0.2.1"
}
},
"node_modules/@floating-ui/dom": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.1.tgz",
- "integrity": "sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==",
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz",
+ "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==",
"dependencies": {
- "@floating-ui/core": "^1.4.1",
- "@floating-ui/utils": "^0.1.1"
+ "@floating-ui/core": "^1.0.0",
+ "@floating-ui/utils": "^0.2.0"
}
},
"node_modules/@floating-ui/react": {
@@ -1059,9 +1059,9 @@
}
},
"node_modules/@floating-ui/utils": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.1.tgz",
- "integrity": "sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw=="
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
+ "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.10",
@@ -1534,38 +1534,38 @@
}
},
"node_modules/@mantine/core": {
- "version": "6.0.18",
- "resolved": "https://registry.npmjs.org/@mantine/core/-/core-6.0.18.tgz",
- "integrity": "sha512-i2Powv0sQYdW8vjJIC7xeo6XJDfGRpavCcQ8JpNVsCoC+db5RToEIsQc3jpgYBPY1/iWeUJuKPMMkQA5V+xMMA==",
+ "version": "6.0.21",
+ "resolved": "https://registry.npmjs.org/@mantine/core/-/core-6.0.21.tgz",
+ "integrity": "sha512-Kx4RrRfv0I+cOCIcsq/UA2aWcYLyXgW3aluAuW870OdXnbII6qg7RW28D+r9D76SHPxWFKwIKwmcucAG08Divg==",
"dependencies": {
"@floating-ui/react": "^0.19.1",
- "@mantine/styles": "6.0.18",
- "@mantine/utils": "6.0.18",
+ "@mantine/styles": "6.0.21",
+ "@mantine/utils": "6.0.21",
"@radix-ui/react-scroll-area": "1.0.2",
"react-remove-scroll": "^2.5.5",
"react-textarea-autosize": "8.3.4"
},
"peerDependencies": {
- "@mantine/hooks": "6.0.18",
+ "@mantine/hooks": "6.0.21",
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@mantine/hooks": {
- "version": "6.0.18",
- "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.18.tgz",
- "integrity": "sha512-i9RxEtC+7t5qG5Gn5SqTHkHxH9MSsGi9mQebt/GitX9+d9BVBwYLqutlDlHvb8bvDteuYphX5VWLSnJXT4Pivw==",
+ "version": "6.0.21",
+ "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.21.tgz",
+ "integrity": "sha512-sYwt5wai25W6VnqHbS5eamey30/HD5dNXaZuaVEAJ2i2bBv8C0cCiczygMDpAFiSYdXoSMRr/SZ2CrrPTzeNew==",
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@mantine/next": {
- "version": "6.0.18",
- "resolved": "https://registry.npmjs.org/@mantine/next/-/next-6.0.18.tgz",
- "integrity": "sha512-NkQpnib4E9EMzAFHKKGB/6YRid1SFRyD6xe3ZmJvHPE9Ug7sbGw3iTVuitZJtKujSZtTq1oY46bcATW3TYdJ5A==",
+ "version": "6.0.21",
+ "resolved": "https://registry.npmjs.org/@mantine/next/-/next-6.0.21.tgz",
+ "integrity": "sha512-McaVZZsmUol3yY92mSJSgcMQKFST97pVxNtI7Z52YocyuTjPPFXmqxF/TFj24A7noh1wzvRCPjfd9HX66sY+iQ==",
"dependencies": {
- "@mantine/ssr": "6.0.18",
- "@mantine/styles": "6.0.18"
+ "@mantine/ssr": "6.0.21",
+ "@mantine/styles": "6.0.21"
},
"peerDependencies": {
"next": "*",
@@ -1574,11 +1574,11 @@
}
},
"node_modules/@mantine/ssr": {
- "version": "6.0.18",
- "resolved": "https://registry.npmjs.org/@mantine/ssr/-/ssr-6.0.18.tgz",
- "integrity": "sha512-gbtLgqnhva2ygeRkbqYl00bZdqoAPnNp17YCwn4BMtxy6ifx0BlsyRI34RTi+hwRSghqHgpNGwkOn/k7+ORoWw==",
+ "version": "6.0.21",
+ "resolved": "https://registry.npmjs.org/@mantine/ssr/-/ssr-6.0.21.tgz",
+ "integrity": "sha512-TVPiz7VxbBntT42UFg4LCRqsv6HM5nvL5d2jBBbFcg9oztJ/5KVGhrtWbu2+kpq/uWWOpmE0sKDs3HQ/qr1PdQ==",
"dependencies": {
- "@mantine/styles": "6.0.18",
+ "@mantine/styles": "6.0.21",
"html-react-parser": "1.4.12"
},
"peerDependencies": {
@@ -1589,9 +1589,9 @@
}
},
"node_modules/@mantine/styles": {
- "version": "6.0.18",
- "resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-6.0.18.tgz",
- "integrity": "sha512-SNKcHkkmqLWUtE9lPucqOGNopvP4VsUtVRVtIUldRwvpzqoeuX1NJH6BwB8GPfVU0Wck/VeEhvGs+XzBe2xTRA==",
+ "version": "6.0.21",
+ "resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-6.0.21.tgz",
+ "integrity": "sha512-PVtL7XHUiD/B5/kZ/QvZOZZQQOj12QcRs3Q6nPoqaoPcOX5+S7bMZLMH0iLtcGq5OODYk0uxlvuJkOZGoPj8Mg==",
"dependencies": {
"clsx": "1.1.1",
"csstype": "3.0.9"
@@ -1608,9 +1608,9 @@
"integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw=="
},
"node_modules/@mantine/utils": {
- "version": "6.0.18",
- "resolved": "https://registry.npmjs.org/@mantine/utils/-/utils-6.0.18.tgz",
- "integrity": "sha512-xvTnAUUHsdpsBm7OrcBueGEPdBwDm7wzUBsHweqSZMjT/HQOf4w4iirefNrFhMD2wNVetNL42kEngEBe6t63/w==",
+ "version": "6.0.21",
+ "resolved": "https://registry.npmjs.org/@mantine/utils/-/utils-6.0.21.tgz",
+ "integrity": "sha512-33RVDRop5jiWFao3HKd3Yp7A9mEq4HAJxJPTuYm1NkdqX6aTKOQK7wT8v8itVodBp+sb4cJK6ZVdD1UurK/txQ==",
"peerDependencies": {
"react": ">=16.8.0"
}
@@ -2392,9 +2392,9 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/aria-hidden": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz",
- "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz",
+ "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==",
"dependencies": {
"tslib": "^2.0.0"
},
@@ -6716,11 +6716,11 @@
"integrity": "sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw=="
},
"node_modules/react-remove-scroll": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.6.tgz",
- "integrity": "sha512-bO856ad1uDYLefgArk559IzUNeQ6SWH4QnrevIUjH+GczV56giDfl3h0Idptf2oIKxQmd1p9BN25jleKodTALg==",
+ "version": "2.5.9",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.9.tgz",
+ "integrity": "sha512-bvHCLBrFfM2OgcrpPY2YW84sPdS2o2HKWJUf1xGyGLnSoEnOTOBpahIarjRuYtN0ryahCeP242yf+5TrBX/pZA==",
"dependencies": {
- "react-remove-scroll-bar": "^2.3.4",
+ "react-remove-scroll-bar": "^2.3.6",
"react-style-singleton": "^2.2.1",
"tslib": "^2.1.0",
"use-callback-ref": "^1.3.0",
@@ -6740,9 +6740,9 @@
}
},
"node_modules/react-remove-scroll-bar": {
- "version": "2.3.4",
- "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz",
- "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==",
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz",
+ "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==",
"dependencies": {
"react-style-singleton": "^2.2.1",
"tslib": "^2.0.0"
@@ -7759,9 +7759,9 @@
}
},
"node_modules/use-callback-ref": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz",
- "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz",
+ "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==",
"dependencies": {
"tslib": "^2.0.0"
},
diff --git a/package.json b/package.json
index bc231e3..072bd1e 100644
--- a/package.json
+++ b/package.json
@@ -17,20 +17,20 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint",
+ "lint": "biome lint .",
"test": "jest",
"e2e": "playwright test",
"e2e-dev": "playwright test --ui",
"vercel-build": "next build",
"build:extension": "extensionReqs/build.sh",
- "format": "@biomejs/biome format --write"
+ "format": "biome format . --write"
},
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/server": "^11.10.0",
- "@mantine/core": "^6.0.8",
- "@mantine/hooks": "^6.0.8",
- "@mantine/next": "^6.0.8",
+ "@mantine/core": "^6.0.21",
+ "@mantine/hooks": "^6.0.21",
+ "@mantine/next": "^6.0.21",
"@tabler/icons-react": "2.17.0",
"@types/node": "18.16.0",
"@types/react": "18.0.38",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index 8d72f6b..b2257ba 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -1,11 +1,11 @@
-import { AppProps } from "next/app"
+import type { AppProps } from "next/app"
import { Analytics } from "@vercel/analytics/react"
import Head from "next/head"
import {
AppShell,
Aside,
Button,
- ColorScheme,
+ type ColorScheme,
ColorSchemeProvider,
Footer,
Group,
@@ -110,7 +110,7 @@ function ExtentionLayout({
colorScheme: string
children: React.ReactNode
- [x: string]: any
+ [x: string]: React.ReactNode
}) {
const extensionWidth = "380px"
const extensionHeight = "400px"
@@ -151,7 +151,7 @@ function DefaultLayout({
}: {
colorScheme: string
children: React.ReactNode
- [x: string]: any
+ [x: string]: React.ReactNode
}) {
return (
+
diff --git a/pages/text.tsx b/pages/text.tsx
index 9631ba9..98c590b 100644
--- a/pages/text.tsx
+++ b/pages/text.tsx
@@ -3,9 +3,7 @@ import { PlaceText } from "../utils/transformer"
import { Box, Text } from "@mantine/core"
import RegexReplacer from "@/components/RegexReplacer"
-type Props = {}
-
-export default function text({}: Props) {
+export default function text() {
return (
diff --git a/playwright.config.ts b/playwright.config.ts
index e8073a1..b4a309a 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -26,7 +26,7 @@ export default defineConfig({
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://localhost:3000",
- testIdAttribute: "data-cy",
+ testIdAttribute: "data-test",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
diff --git a/tests/aliasedEmail.spec.ts b/tests/aliasedEmail.spec.ts
index 8f8cd93..784e11e 100644
--- a/tests/aliasedEmail.spec.ts
+++ b/tests/aliasedEmail.spec.ts
@@ -1,4 +1,4 @@
-import {
+import type {
AliasType,
CopyHistoryType,
TotalLocalStorage,
@@ -96,7 +96,7 @@ test.describe("aliased emails", () => {
expect(typeof parsedCopyHistory[0].timestamp).toBe("number")
// Remove the timestamp property for comparison
- delete parsedCopyHistory[0].timestamp
+ parsedCopyHistory[0].timestamp = undefined
// Now compare the rest of the object
expect(parsedCopyHistory).toEqual([
@@ -141,7 +141,7 @@ test.describe("aliased emails with timestamp", () => {
)
const parsedCopyHistory = copyHistory ? JSON.parse(copyHistory.value) : []
const timestamp = parsedCopyHistory[0].timestamp
- const date = new Date(parseInt(timestamp))
+ const date = new Date(Number.parseInt(timestamp))
expect((date.getUTCMinutes() + date.getUTCSeconds()) / 10000).toBeCloseTo(
(now.getUTCMinutes() + now.getUTCSeconds()) / 10000,
diff --git a/tests/e2e.spec.ts b/tests/e2e.spec.ts
new file mode 100644
index 0000000..2120e75
--- /dev/null
+++ b/tests/e2e.spec.ts
@@ -0,0 +1,32 @@
+import { test, expect } from "@playwright/test"
+
+test.describe("Copying things", () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto("/")
+ })
+
+ test("the same thing copied twice should only show once in the history", async ({
+ page,
+ }) => {
+ await page.getByText("Array").click()
+ await expect(
+ page.getByText(
+ 'Copied ["Lorem","ipsum","dolor","sit","amet","consectetur","adipiscing","elit"]'
+ )
+ ).toBeVisible()
+
+ await page.getByText("1 Sentence").click()
+ await page.getByText("Array").click()
+ await expect(page.getByTestId("copyHistoryItems").first()).toContainText(
+ '["Lorem","ipsum","dolor","sit","amet","consectetur","adipiscing","elit"]'
+ )
+ expect(
+ await page
+ .getByTestId("copyHistoryItems")
+ .getByText(
+ '["Lorem","ipsum","dolor","sit","amet","consectetur","adipiscing","elit"]'
+ )
+ .count()
+ ).toBe(1)
+ })
+})
diff --git a/tests/regex.spec.ts b/tests/regex.spec.ts
new file mode 100644
index 0000000..62c441c
--- /dev/null
+++ b/tests/regex.spec.ts
@@ -0,0 +1,22 @@
+import { test, expect } from "@playwright/test"
+
+test.describe("Regex Replacer", () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto("/")
+ await page.getByTestId("regex-input").fill("e")
+ await page.getByTestId("regex-test-text").fill("test")
+ await page.getByTestId("regex-replacement-text").fill("oa")
+ })
+ test("Regex things copies correctly", async ({ page }) => {
+ await expect(page.getByTestId("regex-preview")).toBeVisible()
+ await page.getByTestId("regex-copy").click()
+ await expect(page.getByTestId("/e/g")).toContainText("0: /e/g")
+ await expect(page.getByTestId("regex-function")).toContainText(
+ '.replace(/e/g, "oa")'
+ )
+ await page.getByTestId("regex-preview").click()
+ await expect(page.getByTestId("copyHistoryItems").first()).toContainText(
+ "toast"
+ )
+ })
+})
diff --git a/utils/colorSelector.ts b/utils/colorSelector.ts
index 4faf654..412bff2 100644
--- a/utils/colorSelector.ts
+++ b/utils/colorSelector.ts
@@ -5,14 +5,17 @@ export const colorSelector = (textElement: string) => {
case "paragraphs":
return "indigo"
case "words":
+ case "string":
return "red"
case "json":
return "orange"
+ case "function":
case "array":
return "yellow"
case "email":
return "blue"
case "list":
+ case "regex":
return "lime"
default:
return "black"
diff --git a/utils/regexReplacer.test.ts b/utils/regexReplacer.test.ts
index f1d889d..32fac5f 100644
--- a/utils/regexReplacer.test.ts
+++ b/utils/regexReplacer.test.ts
@@ -5,53 +5,50 @@ describe("Basic strings", () => {
expect(regexReplacer("world", "universe", "hello world")).toBe(
"hello universe"
)
+ })
- test("Regular Expression Test", () => {
- expect(regexReplacer("l+", "X", "hello world")).toBe("heXo worXd")
- })
+ test("Regular Expression Test", () => {
+ expect(regexReplacer("l+", "X", "hello world")).toBe("heXo worXd")
+ })
- test("Empty String Replacement", () => {
- expect(regexReplacer("world", "", "hello world")).toBe("hello ")
- })
+ test("Empty String Replacement", () => {
+ expect(regexReplacer("world", "", "hello world")).toBe("hello ")
+ })
- test("No Matches", () => {
- expect(regexReplacer("foo", "bar", "hello world")).toBe("hello world")
- })
+ test("No Matches", () => {
+ expect(regexReplacer("foo", "bar", "hello world")).toBe("hello world")
+ })
- test("Case Sensitivity", () => {
- expect(regexReplacer("W", "X", "hello World")).toBe("hello Xorld")
- })
+ test("Case Sensitivity", () => {
+ expect(regexReplacer("W", "X", "hello World")).toBe("hello Xorld")
+ })
- test("Replacement with Special Characters", () => {
- expect(regexReplacer("world", "$100", "hello world")).toBe("hello $100")
- })
+ test("Replacement with Special Characters", () => {
+ expect(regexReplacer("world", "$100", "hello world")).toBe("hello $100")
+ })
- test("Multiple Replacements", () => {
- expect(regexReplacer("o", "X", "hello world")).toBe("hellX wXrld")
- })
+ test("Multiple Replacements", () => {
+ expect(regexReplacer("o", "X", "hello world")).toBe("hellX wXrld")
+ })
- test("Long Test String", () => {
- expect(
- regexReplacer(
- "hello",
- "hi",
- "hello world, hello universe, hello galaxy"
- )
- ).toBe("hi world, hi universe, hi galaxy")
- })
+ test("Long Test String", () => {
+ expect(
+ regexReplacer("hello", "hi", "hello world, hello universe, hello galaxy")
+ ).toBe("hi world, hi universe, hi galaxy")
+ })
- test("Empty Inputs", () => {
- expect(regexReplacer("", "", "")).toBe("")
- })
+ test("Empty Inputs", () => {
+ expect(regexReplacer("", "", "")).toBe("")
})
})
+
describe("Special characters", () => {
test("Money Test", () => {
expect(regexReplacer("[$,]", "", "$2,000")).toBe("2000")
})
test("Special Characters and texts", () => {
- expect(regexReplacer("$2", "", "$2,000")).toBe(",000")
+ expect(regexReplacer("\\$2", "", "$2,000")).toBe(",000")
})
test("Dollar Sign Replacement", () => {
diff --git a/utils/regexReplacer.ts b/utils/regexReplacer.ts
index f4a1068..5ccc8c4 100644
--- a/utils/regexReplacer.ts
+++ b/utils/regexReplacer.ts
@@ -1,8 +1,10 @@
+import { stringToRegex, performReplacement } from "./transformStringToRegex"
+
export function regexReplacer(
pattern: string,
replacement: string,
testString: string
-) {
+): string {
try {
// Handle special case where dollar sign should not be removed
if (pattern === "$" && replacement === "") {
@@ -10,24 +12,13 @@ export function regexReplacer(
return testString.replace(/\$(?=\d|,)/g, "")
}
- // For other patterns and replacements, perform regular replacement
- const regex = new RegExp(pattern, "g") // 'g' flag for global search and replace
- const replacedString = testString.replace(regex, replacement)
-
- // If the replacement pattern contains special characters that should be escaped, do so
- const escapedReplacement = replacement.replace(
- /[.*+?^${}()|[\]\\]/g,
- "\\$&"
- )
+ // Transform the pattern string into a regular expression
+ const regex = stringToRegex(pattern)
- // If the replacement string contains special characters that might interfere with regex replacement,
- // escape them using $1, $2, etc.
- return replacedString.replace(
- new RegExp(escapedReplacement, "g"),
- replacement
- )
+ // Perform the replacement using the regular expression
+ return performReplacement(regex, replacement, testString)
} catch (error) {
console.error("Error in regexReplacer:", error)
- return testString // Return the original string if there's an error with the regex
+ return testString // Return the original string if there's an error
}
}
diff --git a/utils/transformStringToRegex.ts b/utils/transformStringToRegex.ts
new file mode 100644
index 0000000..afbec6e
--- /dev/null
+++ b/utils/transformStringToRegex.ts
@@ -0,0 +1,35 @@
+export function stringToRegex(pattern: string): RegExp {
+ try {
+ // Attempt to create RegExp object with the pattern
+ return new RegExp(pattern, "g") // 'g' flag for global search and replace
+ } catch (error) {
+ return /(?:)/ // Return an empty RegExp object if there's an error
+ }
+}
+
+export function performReplacement(
+ regex: RegExp,
+ replacement: string,
+ testString: string
+): string {
+ try {
+ // Perform regular replacement
+ const replacedString = testString.replace(regex, replacement)
+
+ // If the replacement pattern contains special characters that should be escaped, do so
+ const escapedReplacement = replacement.replace(
+ /[.*+?^${}()|[\]\\]/g,
+ "\\$&"
+ )
+
+ // If the replacement string contains special characters that might interfere with regex replacement,
+ // escape them using $1, $2, etc.
+ return replacedString.replace(
+ new RegExp(escapedReplacement, "g"),
+ replacement
+ )
+ } catch (error) {
+ console.error("Error in performReplacement:", error)
+ throw error // Throw the error if there's an issue performing the replacement
+ }
+}
diff --git a/utils/transformer.ts b/utils/transformer.ts
index 408e6c1..cb75595 100644
--- a/utils/transformer.ts
+++ b/utils/transformer.ts
@@ -33,7 +33,7 @@ export const PlaceText = ({
depth,
theme,
}: Composition) => {
- theme === undefined && (theme = "lorem")
+ theme = theme ? theme : lorem
switch (theme) {
case "lorem":
@@ -93,10 +93,9 @@ function handleText(
case "json":
if (depth !== undefined) {
return makeJson(count, depth, inputText)
- } else {
- console.error("Depth is undefined in json type")
- return "Depth is undefined in json type"
}
+ console.error("Depth is undefined in json type")
+ return "Depth is undefined in json type"
default:
return "I'm not sure what you want me to do"