diff --git a/package.json b/package.json index 89fed03f..5ba823a5 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,6 @@ "focus-trap-react": "^10.2.1", "fuse.js": "^7.0.0", "google-protobuf": "^3.21.2", - "imgur": "^2.4.2", "javascript-time-ago": "^2.5.9", "linkify-plugin-mention": "^4.1.3", "linkify-react": "^4.1.1", @@ -185,4 +184,4 @@ "supabase": "^1.123.0", "ts-jest": "^29.2.4" } -} +} \ No newline at end of file diff --git a/pages/_app.tsx b/pages/_app.tsx index e18f0e70..508c0479 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -93,12 +93,7 @@ export default function MyApp({ Component, pageProps }: AppProps) { ); return ( - + {children} ); diff --git a/src/common/components/Editor/NewCastEditor.tsx b/src/common/components/Editor/NewCastEditor.tsx index c5c98269..bba8e5ff 100644 --- a/src/common/components/Editor/NewCastEditor.tsx +++ b/src/common/components/Editor/NewCastEditor.tsx @@ -5,6 +5,7 @@ import { DraftStatus, DraftType } from "../../constants/farcaster"; import { useHotkeys } from "react-hotkeys-hook"; import { useEditor, EditorContent } from "@mod-protocol/react-editor"; import { EmbedsEditor } from "@mod-protocol/react-ui-shadcn/dist/lib/embeds"; + import { ModManifest, fetchUrlMetadata, @@ -17,11 +18,7 @@ import { createRenderMentionsSuggestionConfig } from "@mod-protocol/react-ui-sha import { Button } from "@/components/ui/button"; import { take } from "lodash"; import { ChannelPicker } from "../ChannelPicker"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { CreationMod } from "@mod-protocol/react"; import { creationMods } from "@mod-protocol/mod-registry"; import { renderers } from "@mod-protocol/react-ui-shadcn/dist/renderers"; @@ -43,12 +40,11 @@ import { openSourcePlanLimits } from "@/config/customerLimitation"; import Link from "next/link"; import { isPaidUser } from "@/stores/useUserStore"; import { MentionList } from "../MentionsList"; +import { useImgurUpload } from "@/common/hooks/useImgurUpload"; const API_URL = process.env.NEXT_PUBLIC_MOD_PROTOCOL_API_URL!; const getMentions = getFarcasterMentions(API_URL); -const neynarClient = new NeynarAPIClient( - process.env.NEXT_PUBLIC_NEYNAR_API_KEY! -); +const neynarClient = new NeynarAPIClient(process.env.NEXT_PUBLIC_NEYNAR_API_KEY!); const getChannels = async (query: string): Promise => { let channels: Channel[] = []; @@ -94,16 +90,13 @@ export default function NewPostEntry({ hideSchedule, }: NewPostEntryProps) { const posthog = usePostHog(); - const { addScheduledDraft, updatePostDraft, publishPostDraft } = - useDraftStore(); + const { addScheduledDraft, updatePostDraft, publishPostDraft } = useDraftStore(); const [currentMod, setCurrentMod] = React.useState(null); const [initialEmbeds, setInitialEmbeds] = React.useState(); const [scheduleDateTime, setScheduleDateTime] = React.useState(); const hasEmbeds = draft?.embeds && !!draft.embeds.length; - const account = useAccountStore( - (state) => state.accounts[state.selectedAccountIdx] - ); + const account = useAccountStore((state) => state.accounts[state.selectedAccountIdx]); const { allChannels } = useAccountStore(); const isReply = draft?.parentCastId !== undefined; @@ -160,55 +153,86 @@ export default function NewPostEntry({ return true; }; - const ref = useHotkeys( - "meta+enter", - onSubmitPost, - [onSubmitPost, draft, account], - { - enableOnFormTags: true, + const ref = useHotkeys("meta+enter", onSubmitPost, [onSubmitPost, draft, account], { + enableOnFormTags: true, + }); + + const { uploadImage, isUploading, error, image } = useImgurUpload(); + + useEffect(() => { + if (isUploading) { + toast.loading("Uploading image...", { + id: "image-upload", + }); + } else if (image) { + toast.success("Image uploaded", { + id: "image-upload", + }); + + if (!embeds.find((embed) => "url" in embed && embed.url === image.link)) { + setEmbeds([ + ...embeds, + { + status: "loaded", + url: image.link, + metadata: { + image: { + url: image.link, + width: image.width, + height: image.height, + }, + }, + }, + ]); + } + } else if (error) { + console.error("failed uploading to imgur", error); + toast.error(error, { + id: "image-upload", + }); } - ); + }, [isUploading, error, image]); const isPublishing = draft?.status === DraftStatus.publishing; - const { - editor, - getText, - addEmbed, - getEmbeds, - setEmbeds, - setChannel, - getChannel, - handleSubmit, - setText, - } = useEditor({ - fetchUrlMetadata: getUrlMetadata, - onError, - onSubmit: onSubmitPost, - linkClassName: "text-blue-500", - renderChannelsSuggestionConfig: createRenderMentionsSuggestionConfig({ - getResults: getChannels, - RenderList: ChannelList, - }), - renderMentionsSuggestionConfig: createRenderMentionsSuggestionConfig({ - getResults: getMentions, - RenderList: MentionList, - }), - editorOptions: { - parseOptions: { - preserveWhitespace: "full", + const { editor, getText, addEmbed, getEmbeds, setEmbeds, setChannel, getChannel, handleSubmit, setText } = + useEditor({ + fetchUrlMetadata: getUrlMetadata, + onError, + onSubmit: onSubmitPost, + linkClassName: "text-blue-500", + renderChannelsSuggestionConfig: createRenderMentionsSuggestionConfig({ + getResults: getChannels, + RenderList: ChannelList, + }), + renderMentionsSuggestionConfig: createRenderMentionsSuggestionConfig({ + getResults: getMentions, + RenderList: MentionList, + }), + editorOptions: { + editorProps: { + handlePaste: (view, event) => + extractImageAndUpload({ + data: event.clipboardData, + uploadImage, + }), + handleDrop: (view, event) => + extractImageAndUpload({ + data: event.dataTransfer, + uploadImage, + }), + }, + + parseOptions: { + preserveWhitespace: "full", + }, }, - }, - }); + }); useEffect(() => { if (!text && draft?.text && isEmpty(draft.mentionsToFids)) { - editor?.commands.setContent( - `

${draft.text.replace(/\n/g, "
")}

`, - true, - { - preserveWhitespace: "full", - } - ); + editor?.commands.setContent(`

${draft.text.replace(/\n/g, "
")}

`, true, { + preserveWhitespace: "full", + }); } if (draft?.embeds) { @@ -262,25 +286,18 @@ export default function NewPostEntry({ }, [draft?.parentUrl]); const getButtonText = () => { - if (isPublishing) - return scheduleDateTime ? "Scheduling..." : "Publishing..."; + if (isPublishing) return scheduleDateTime ? "Scheduling..." : "Publishing..."; - return `${scheduleDateTime ? "Schedule" : "Cast"}${ - account ? ` as ${account.name}` : "" - }`; + return `${scheduleDateTime ? "Schedule" : "Cast"}${account ? ` as ${account.name}` : ""}`; }; const scheduledCastCount = - useDraftStore((state) => - state.drafts.filter((draft) => draft.status === DraftStatus.scheduled) - )?.length || 0; + useDraftStore((state) => state.drafts.filter((draft) => draft.status === DraftStatus.scheduled)) + ?.length || 0; const hasReachedFreePlanLimit = - !isPaidUser() && - scheduledCastCount >= openSourcePlanLimits.maxScheduledCasts; + !isPaidUser() && scheduledCastCount >= openSourcePlanLimits.maxScheduledCasts; const isButtonDisabled = - isPublishing || - !textLengthIsValid || - (scheduleDateTime && hasReachedFreePlanLimit); + isPublishing || !textLengthIsValid || (scheduleDateTime && hasReachedFreePlanLimit); if (!draft) return null; @@ -305,11 +322,7 @@ export default function NewPostEntry({ autoFocus className="w-full h-full min-h-[150px] text-foreground/80" /> -
} - /> +
} />
)} @@ -364,9 +377,7 @@ export default function NewPostEntry({ {textLengthWarning && ( -
- {textLengthWarning} -
+
{textLengthWarning}
)}
{onRemove && ( @@ -413,6 +424,7 @@ export default function NewPostEntry({
+ {hasEmbeds && (
{map(draft.embeds, (embed) => ( @@ -420,9 +432,7 @@ export default function NewPostEntry({ {renderEmbedForUrl({ ...embed, onRemove: () => { - const newEmbeds = draft.embeds.filter( - (e) => e.url !== embed.url - ); + const newEmbeds = draft.embeds.filter((e) => e.url !== embed.url); updatePostDraft(draftIdx, { ...draft, embeds: newEmbeds }); window.location.reload(); }, @@ -434,3 +444,26 @@ export default function NewPostEntry({
); } + +function extractImageAndUpload(args: { + data: DataTransfer | null; + uploadImage: (file: File) => void; +}): boolean { + const { data, uploadImage } = args; + + if (!data) { + return false; + } + + const items = Array.from(data.items); + for (const item of items) { + if (item.type.indexOf("image") === 0) { + const file = item.getAsFile(); + if (file) { + uploadImage(file); + return true; + } + } + } + return false; +} diff --git a/src/common/components/ImgurUpload.tsx b/src/common/components/ImgurUpload.tsx index 1912c535..47dc7e28 100644 --- a/src/common/components/ImgurUpload.tsx +++ b/src/common/components/ImgurUpload.tsx @@ -1,14 +1,8 @@ -import React, { useRef, useState } from "react"; -import { ImgurClient } from "imgur"; -import { Button } from "@/components/ui/button"; +import React, { useRef } from "react"; import { Progress } from "@/components/ui/progress"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; - -const client = new ImgurClient({ - clientId: process.env.NEXT_PUBLIC_IMGUR_CLIENT_ID, - clientSecret: process.env.NEXT_PUBLIC_IMGUR_CLIENT_SECRET, -}); +import { useImgurUpload } from "@/common/hooks/useImgurUpload"; type ImgurUploadProps = { onSuccess?: (string) => void; @@ -16,57 +10,22 @@ type ImgurUploadProps = { const ImgurUpload = ({ onSuccess }: ImgurUploadProps) => { const fileInputRef = useRef(null); - const [uploadProgress, setUploadProgress] = useState(0); - const [error, setError] = useState(); - - client.on("uploadProgress", (progress) => { - console.log("uploadProgress", progress); - setUploadProgress(progress?.percent); - }); - - const validateFile = (file: File | undefined): boolean => { - if (!file) { - console.error("No file selected for upload."); - setError("No file selected for upload."); - return false; - } + const { uploadImage, isUploading, error, uploadProgress, image } = useImgurUpload(); - const validImageTypes = ["image/gif", "image/jpeg", "image/png"]; - if (file && !validImageTypes.includes(file.type)) { - console.error("Invalid file type. Please select an image file."); - return false; - } - - return true; - }; - - const handleUpload = (e: React.FormEvent) => { + const handleUpload = async (e: React.FormEvent) => { e.preventDefault(); - setError(undefined); - setUploadProgress(0); - const file = e.currentTarget.files?.[0]; - if (!validateFile(file)) return; - - console.log("file", file); - client - .upload({ image: file?.stream() }) - .then((response) => { - if (response.success) { - setUploadProgress(100); - onSuccess?.(response.data.link); - } else { - setError(`Failed to upload - ${response.data}`); - } - console.log("response", response); - console.log(response.data); - }) - .catch((err) => { - setError(err.message); - console.error(err); - }); + if (file) { + await uploadImage(file); + } }; + React.useEffect(() => { + if (image?.link) { + onSuccess?.(image.link); + } + }, [image, onSuccess]); + return (
@@ -75,15 +34,11 @@ const ImgurUpload = ({ onSuccess }: ImgurUploadProps) => { type="file" ref={fileInputRef} className="h-9 pt-1.5" - onInput={(e) => { - handleUpload(e); - }} + onInput={handleUpload} + disabled={isUploading} /> - {/* */}
- {uploadProgress !== 0 && !error && ( + {uploadProgress > 0 && !error && ( { + const [uploadState, setUploadState] = useState({ + isUploading: false, + error: null, + uploadProgress: 0, + image: null, + }); + + const uploadImage = useCallback(async (file: File): Promise => { + const validateFile = (file: File): boolean => { + const validImageTypes = ["image/gif", "image/jpeg", "image/png"]; + return validImageTypes.includes(file.type); + }; + + if (!validateFile(file)) { + setUploadState({ + isUploading: false, + error: "That file type isn't supported. Gifs, jpegs, and pngs only.", + uploadProgress: 0, + image: null, + }); + return; + } + + setUploadState({ + isUploading: true, + error: null, + uploadProgress: 0, + image: null, + }); + + try { + const formData = new FormData(); + formData.append("image", file); + + const response = await axios.post("https://api.imgur.com/3/image", formData, { + headers: { + Authorization: `Client-ID ${process.env.NEXT_PUBLIC_IMGUR_CLIENT_ID}`, + "Content-Type": "multipart/form-data", + }, + onUploadProgress: (progressEvent) => { + const progress = progressEvent.total + ? Math.round((progressEvent.loaded * 100) / progressEvent.total) + : 0; + setUploadState((prev) => ({ + ...prev, + uploadProgress: progress, + })); + }, + }); + + setUploadState({ + isUploading: false, + error: null, + uploadProgress: 100, + image: response.data.data, + }); + } catch (error) { + if (axios.isAxiosError(error)) { + setUploadState({ + isUploading: false, + error: error.response?.data?.error || "Failed to upload", + uploadProgress: 0, + image: null, + }); + } else { + setUploadState({ + isUploading: false, + error: (error as Error).message, + uploadProgress: 0, + image: null, + }); + } + } + }, []); + + return { + uploadImage, + ...uploadState, + }; +}; diff --git a/yarn.lock b/yarn.lock index 8ad5e77b..233778fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7131,13 +7131,6 @@ axios@1.6.8: form-data "^4.0.0" proxy-from-env "^1.1.0" -axios@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" - integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== - dependencies: - follow-redirects "^1.14.7" - axios@^1.5.0, axios@^1.6.2, axios@^1.6.7: version "1.7.2" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" @@ -9362,7 +9355,7 @@ focus-trap@^7.5.4: dependencies: tabbable "^6.2.0" -follow-redirects@^1.14.7, follow-redirects@^1.15.6: +follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -9915,15 +9908,6 @@ ignore@^5.2.0, ignore@^5.2.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== -imgur@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/imgur/-/imgur-2.4.2.tgz#e1a1ce33210f81c57df06c47be824c7c941ed46c" - integrity sha512-SBbslP2yJG13qHEc5I5Rg4SVBRM4dVstpPNJ1diHFvvzyizP9NZ6JD5OdwmHBRUxaAdAg/igY8/Fa6K2ireR0g== - dependencies: - axios "^0.25.0" - form-data "^4.0.0" - whatwg-url "^14.0.0" - import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -12850,7 +12834,7 @@ punycode.js@^2.3.1: resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7" integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA== -punycode@^2.1.0, punycode@^2.3.1: +punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -13878,16 +13862,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13979,14 +13954,7 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -14313,13 +14281,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -tr46@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec" - integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g== - dependencies: - punycode "^2.3.1" - tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -14909,11 +14870,6 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== -webidl-conversions@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" - integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== - webpack-sources@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" @@ -14924,14 +14880,6 @@ webpack-virtual-modules@^0.5.0: resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz#362f14738a56dae107937ab98ea7062e8bdd3b6c" integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw== -whatwg-url@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.0.0.tgz#00baaa7fd198744910c4b1ef68378f2200e4ceb6" - integrity sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw== - dependencies: - tr46 "^5.0.0" - webidl-conversions "^7.0.0" - whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -15007,7 +14955,7 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -15025,15 +14973,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"