Merge pull request #720 from linkwarden/revert-719-feat/customizable-links

Revert "Feat/customizable links"
This commit is contained in:
Daniel 2024-08-18 14:47:29 -04:00 committed by GitHub
commit 03b4240b8b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 326 additions and 976 deletions

View File

@ -1,36 +0,0 @@
import React from "react";
type Props = {
text: string;
};
const CopyButton = ({ text }: Props) => {
return (
<div
className="bi-copy text-xl text-neutral btn btn-sm btn-square btn-ghost"
onClick={() => {
try {
navigator.clipboard.writeText(text).then(() => {
const copyIcon = document.querySelector(".bi-copy");
if (copyIcon) {
copyIcon.classList.remove("bi-copy");
copyIcon.classList.add("bi-check2");
copyIcon.classList.add("text-success");
}
setTimeout(() => {
if (copyIcon) {
copyIcon.classList.remove("bi-check2");
copyIcon.classList.remove("text-success");
copyIcon.classList.add("bi-copy");
}
}, 1000);
});
} catch (err) {
console.log(err);
}
}}
></div>
);
};
export default CopyButton;

View File

@ -14,12 +14,7 @@ export default function dashboardItem({
</div>
<div className="ml-4 flex flex-col justify-center">
<p className="text-neutral text-xs tracking-wider">{name}</p>
<p className="font-thin text-5xl text-primary mt-0.5 hidden sm:block md:hidden">
{value < 1000 ? value : (value / 1000).toFixed(1) + "k"}
</p>
<p className="font-thin text-5xl text-primary mt-0.5 sm:hidden md:block">
{value}
</p>
<p className="font-thin text-5xl text-primary mt-0.5">{value}</p>
</div>
</div>
);

View File

@ -1,88 +0,0 @@
import React, { ReactNode, useEffect } from "react";
import ClickAwayHandler from "@/components/ClickAwayHandler";
import { Drawer as D } from "vaul";
type Props = {
toggleDrawer: Function;
children: ReactNode;
className?: string;
dismissible?: boolean;
};
export default function Drawer({
toggleDrawer,
className,
children,
dismissible = true,
}: Props) {
const [drawerIsOpen, setDrawerIsOpen] = React.useState(true);
useEffect(() => {
if (window.innerWidth >= 640) {
document.body.style.overflow = "hidden";
document.body.style.position = "relative";
return () => {
document.body.style.overflow = "auto";
document.body.style.position = "";
};
}
}, []);
if (window.innerWidth < 640) {
return (
<D.Root
open={drawerIsOpen}
onClose={() => dismissible && setTimeout(() => toggleDrawer(), 350)}
dismissible={dismissible}
>
<D.Portal>
<D.Overlay className="fixed inset-0 bg-black/40" />
<ClickAwayHandler
onClickOutside={() => dismissible && setDrawerIsOpen(false)}
>
<D.Content className="flex flex-col rounded-t-2xl min-h-max mt-24 fixed bottom-0 left-0 right-0 z-30 h-[90%]">
<div
className="p-4 bg-base-100 rounded-t-2xl flex-1 border-neutral-content border-t overflow-y-auto"
data-testid="mobile-modal-container"
>
<div
className="mx-auto w-12 h-1.5 flex-shrink-0 rounded-full bg-neutral mb-5"
data-testid="mobile-modal-slider"
/>
{children}
</div>
</D.Content>
</ClickAwayHandler>
</D.Portal>
</D.Root>
);
} else {
return (
<D.Root
open={drawerIsOpen}
onClose={() => dismissible && setTimeout(() => toggleDrawer(), 350)}
dismissible={dismissible}
direction="right"
>
<D.Portal>
<D.Overlay className="fixed inset-0 bg-black/10 z-20" />
<ClickAwayHandler
onClickOutside={() => dismissible && setDrawerIsOpen(false)}
className="z-30"
>
<D.Content className="bg-white flex flex-col h-full w-2/5 min-w-[30rem] mt-24 fixed bottom-0 right-0 z-40 !select-auto">
<div
className={
"p-4 bg-base-100 flex-1 border-neutral-content border-l overflow-y-auto " +
className
}
>
{children}
</div>
</D.Content>
</ClickAwayHandler>
</D.Portal>
</D.Root>
);
}
}

View File

@ -1,46 +0,0 @@
import { icons } from "@/lib/client/icons";
import React, { useMemo, useState } from "react";
import Fuse from "fuse.js";
import TextInput from "./TextInput";
const fuse = new Fuse(icons, {
keys: [{ name: "name", weight: 4 }, "tags", "categories"],
threshold: 0.2,
useExtendedSearch: true,
});
type Props = {};
const IconPicker = (props: Props) => {
const [query, setQuery] = useState("");
const filteredQueryResultsSelector = useMemo(() => {
if (!query) {
return icons;
}
return fuse.search(query).map((result) => result.item);
}, [query]);
return (
<div className="w-fit">
<TextInput
className="p-2 rounded w-full mb-5"
placeholder="Search icons"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<div className="grid grid-cols-6 gap-5 w-fit">
{filteredQueryResultsSelector.map((icon) => {
const IconComponent = icon.Icon;
return (
<div key={icon.name} onClick={() => console.log(icon.name)}>
<IconComponent size={32} weight="fill" />
</div>
);
})}
</div>
</div>
);
};
export default IconPicker;

View File

@ -1,305 +0,0 @@
import React, { useEffect, useState } from "react";
import {
LinkIncludingShortenedCollectionAndTags,
ArchivedFormat,
} from "@/types/global";
import Link from "next/link";
import { useSession } from "next-auth/react";
import {
pdfAvailable,
readabilityAvailable,
monolithAvailable,
screenshotAvailable,
} from "@/lib/shared/getArchiveValidity";
import PreservedFormatRow from "@/components/PreserverdFormatRow";
import getPublicUserData from "@/lib/client/getPublicUserData";
import { useTranslation } from "next-i18next";
import { BeatLoader } from "react-spinners";
import { useUser } from "@/hooks/store/user";
import { useGetLink } from "@/hooks/store/links";
import LinkIcon from "./LinkViews/LinkComponents/LinkIcon";
import CopyButton from "./CopyButton";
import { useRouter } from "next/router";
type Props = {
className?: string;
link: LinkIncludingShortenedCollectionAndTags;
};
export default function LinkDetails({ className, link }: Props) {
const { t } = useTranslation();
const session = useSession();
const getLink = useGetLink();
const { data: user = {} } = useUser();
const [collectionOwner, setCollectionOwner] = useState({
id: null as unknown as number,
name: "",
username: "",
image: "",
archiveAsScreenshot: undefined as unknown as boolean,
archiveAsMonolith: undefined as unknown as boolean,
archiveAsPDF: undefined as unknown as boolean,
});
useEffect(() => {
const fetchOwner = async () => {
if (link.collection.ownerId !== user.id) {
const owner = await getPublicUserData(
link.collection.ownerId as number
);
setCollectionOwner(owner);
} else if (link.collection.ownerId === user.id) {
setCollectionOwner({
id: user.id as number,
name: user.name,
username: user.username as string,
image: user.image as string,
archiveAsScreenshot: user.archiveAsScreenshot as boolean,
archiveAsMonolith: user.archiveAsScreenshot as boolean,
archiveAsPDF: user.archiveAsPDF as boolean,
});
}
};
fetchOwner();
}, [link.collection.ownerId]);
const isReady = () => {
return (
link &&
(collectionOwner.archiveAsScreenshot === true
? link.pdf && link.pdf !== "pending"
: true) &&
(collectionOwner.archiveAsMonolith === true
? link.monolith && link.monolith !== "pending"
: true) &&
(collectionOwner.archiveAsPDF === true
? link.pdf && link.pdf !== "pending"
: true) &&
link.readable &&
link.readable !== "pending"
);
};
const atLeastOneFormatAvailable = () => {
return (
screenshotAvailable(link) ||
pdfAvailable(link) ||
readabilityAvailable(link) ||
monolithAvailable(link)
);
};
useEffect(() => {
(async () => {
await getLink.mutateAsync({
id: link.id as number,
});
})();
let interval: any;
if (!isReady()) {
interval = setInterval(async () => {
await getLink.mutateAsync({
id: link.id as number,
});
}, 5000);
} else {
if (interval) {
clearInterval(interval);
}
}
return () => {
if (interval) {
clearInterval(interval);
}
};
}, [link?.monolith]);
const router = useRouter();
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
return (
<div className={className} data-vaul-no-drag>
<LinkIcon link={link} className="mx-auto" />
{link.name && <p className="text-xl text-center mt-2">{link.name}</p>}
{link.url && (
<>
<br />
<p className="text-sm mb-2 text-neutral">{t("link")}</p>
<div className="relative">
<div className="rounded-lg p-2 bg-base-200 hide-scrollbar overflow-x-auto whitespace-nowrap flex justify-between items-center gap-2 pr-14">
<Link href={link.url} title={link.url} target="_blank">
{link.url}
</Link>
<div className="absolute right-0 px-2 bg-base-200">
<CopyButton text={link.url} />
</div>
</div>
</div>
</>
)}
<br />
<p className="text-sm mb-2 text-neutral">{t("collection")}</p>
<div className="relative">
<Link
href={
isPublicRoute
? `/public/collections/${link.collection.id}`
: `/collections/${link.collection.id}`
}
className="rounded-lg p-2 bg-base-200 hide-scrollbar overflow-x-auto whitespace-nowrap flex justify-between items-center gap-2 pr-14"
>
<p>{link.collection.name}</p>
<div className="absolute right-0 px-2 bg-base-200">
<i
className="bi-folder-fill text-xl"
style={{ color: link.collection.color }}
></i>
</div>
</Link>
</div>
{link.tags[0] && (
<>
<br />
<div>
<p className="text-sm mb-2 text-neutral">{t("tags")}</p>
</div>
<div className="flex gap-2">
{link.tags.map((tag) =>
isPublicRoute ? (
<div key={tag.id} className="rounded-lg px-3 py-1 bg-base-200">
{tag.name}
</div>
) : (
<Link
href={"/tags/" + tag.id}
key={tag.id}
className="rounded-lg px-3 py-1 bg-base-200"
>
{tag.name}
</Link>
)
)}
</div>
</>
)}
{link.description && (
<>
<br />
<div>
<p className="text-sm mb-2 text-neutral">{t("notes")}</p>
<div className="rounded-lg p-2 bg-base-200 hyphens-auto">
<p>{link.description}</p>
</div>
</div>
</>
)}
<br />
<p className="text-sm mb-2 text-neutral" title={t("available_formats")}>
{link.url ? t("preserved_formats") : t("file")}
</p>
<div className={`flex flex-col gap-3`}>
{monolithAvailable(link) ? (
<PreservedFormatRow
name={t("webpage")}
icon={"bi-filetype-html"}
format={ArchivedFormat.monolith}
link={link}
downloadable={true}
/>
) : undefined}
{screenshotAvailable(link) ? (
<PreservedFormatRow
name={t("screenshot")}
icon={"bi-file-earmark-image"}
format={
link?.image?.endsWith("png")
? ArchivedFormat.png
: ArchivedFormat.jpeg
}
link={link}
downloadable={true}
/>
) : undefined}
{pdfAvailable(link) ? (
<PreservedFormatRow
name={t("pdf")}
icon={"bi-file-earmark-pdf"}
format={ArchivedFormat.pdf}
link={link}
downloadable={true}
/>
) : undefined}
{readabilityAvailable(link) ? (
<PreservedFormatRow
name={t("readable")}
icon={"bi-file-earmark-text"}
format={ArchivedFormat.readability}
link={link}
/>
) : undefined}
{!isReady() && !atLeastOneFormatAvailable() ? (
<div className={`w-full h-full flex flex-col justify-center p-10`}>
<BeatLoader
color="oklch(var(--p))"
className="mx-auto mb-3"
size={30}
/>
<p className="text-center text-2xl">{t("preservation_in_queue")}</p>
<p className="text-center text-lg">{t("check_back_later")}</p>
</div>
) : link.url && !isReady() && atLeastOneFormatAvailable() ? (
<div className={`w-full h-full flex flex-col justify-center p-5`}>
<BeatLoader
color="oklch(var(--p))"
className="mx-auto mb-3"
size={20}
/>
<p className="text-center">{t("there_are_more_formats")}</p>
<p className="text-center text-sm">{t("check_back_later")}</p>
</div>
) : undefined}
{link.url && (
<Link
href={`https://web.archive.org/web/${link?.url?.replace(
/(^\w+:|^)\/\//,
""
)}`}
target="_blank"
className="text-neutral mx-auto duration-100 hover:opacity-60 flex gap-2 w-1/2 justify-center items-center text-sm"
>
<p className="whitespace-nowrap">{t("view_latest_snapshot")}</p>
<i className="bi-box-arrow-up-right" />
</Link>
)}
</div>
</div>
);
}

View File

@ -12,20 +12,22 @@ import { useTranslation } from "next-i18next";
import { useUser } from "@/hooks/store/user";
import { useDeleteLink, useUpdateLink } from "@/hooks/store/links";
import toast from "react-hot-toast";
import LinkDetailModal from "@/components/ModalContent/LinkDetailModal";
import { useRouter } from "next/router";
type Props = {
link: LinkIncludingShortenedCollectionAndTags;
collection: CollectionIncludingMembersAndLinkCount;
position?: string;
toggleShowInfo?: () => void;
linkInfo?: boolean;
alignToTop?: boolean;
flipDropdown?: boolean;
};
export default function LinkActions({
link,
toggleShowInfo,
position,
linkInfo,
alignToTop,
flipDropdown,
}: Props) {
@ -34,7 +36,6 @@ export default function LinkActions({
const permissions = usePermissions(link.collection.id as number);
const [editLinkModal, setEditLinkModal] = useState(false);
const [linkDetailModal, setLinkDetailModal] = useState(false);
const [deleteLinkModal, setDeleteLinkModal] = useState(false);
const [preservedFormatsModal, setPreservedFormatsModal] = useState(false);
@ -69,24 +70,8 @@ export default function LinkActions({
);
};
const router = useRouter();
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
return (
<>
{isPublicRoute ? (
<div
className={`absolute ${position || "top-3 right-3"} ${
alignToTop ? "" : "dropdown-end"
} z-20`}
onClick={() => setLinkDetailModal(true)}
>
<div className="btn btn-ghost btn-sm btn-square text-neutral">
<i title="More" className="bi-three-dots text-xl" />
</div>
</div>
) : (
<div
className={`dropdown dropdown-left absolute ${
position || "top-3 right-3"
@ -105,8 +90,6 @@ export default function LinkActions({
alignToTop ? "" : "translate-y-10"
}`}
>
{permissions === true ||
(permissions?.canUpdate && (
<li>
<div
role="button"
@ -122,20 +105,21 @@ export default function LinkActions({
: t("pin_to_dashboard")}
</div>
</li>
))}
{linkInfo !== undefined && toggleShowInfo ? (
<li>
<div
role="button"
tabIndex={0}
onClick={() => {
(document?.activeElement as HTMLElement)?.blur();
setLinkDetailModal(true);
toggleShowInfo();
}}
className="whitespace-nowrap"
>
{t("show_link_details")}
{!linkInfo ? t("show_link_details") : t("hide_link_details")}
</div>
</li>
) : undefined}
{permissions === true || permissions?.canUpdate ? (
<li>
<div
@ -199,7 +183,6 @@ export default function LinkActions({
) : undefined}
</ul>
</div>
)}
{editLinkModal ? (
<EditLinkModal
@ -219,13 +202,9 @@ export default function LinkActions({
link={link}
/>
) : undefined}
{linkDetailModal ? (
<LinkDetailModal
onClose={() => setLinkDetailModal(false)}
onEdit={() => setEditLinkModal(true)}
link={link}
/>
) : undefined}
{/* {expandedLink ? (
<ExpandedLink onClose={() => setExpandedLink(false)} link={link} />
) : undefined} */}
</>
);
}

View File

@ -22,7 +22,6 @@ import { useTranslation } from "next-i18next";
import { useCollections } from "@/hooks/store/collections";
import { useUser } from "@/hooks/store/user";
import { useGetLink, useLinks } from "@/hooks/store/links";
import { useRouter } from "next/router";
type Props = {
link: LinkIncludingShortenedCollectionAndTags;
@ -91,10 +90,6 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
const isVisible = useOnScreen(ref);
const permissions = usePermissions(collection?.id as number);
const router = useRouter();
let isPublic = router.pathname.startsWith("/public") ? true : undefined;
useEffect(() => {
let interval: any;
@ -104,7 +99,7 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
link.preview !== "unavailable"
) {
interval = setInterval(async () => {
getLink.mutateAsync({ id: link.id as number, isPublicRoute: isPublic });
getLink.mutateAsync(link.id as number);
}, 5000);
}
@ -115,6 +110,8 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
};
}, [isVisible, link.preview]);
const [showInfo, setShowInfo] = useState(false);
const selectedStyle = selectedLinks.some(
(selectedLink) => selectedLink.id === link.id
)
@ -199,10 +196,63 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
</div>
</div>
{showInfo && (
<div className="p-3 absolute z-30 top-0 left-0 right-0 bottom-0 bg-base-200 rounded-[0.9rem] fade-in overflow-y-auto">
<div
onClick={() => setShowInfo(!showInfo)}
className=" float-right btn btn-sm outline-none btn-circle btn-ghost z-10"
>
<i className="bi-x text-neutral text-2xl"></i>
</div>
<p className="text-neutral text-lg font-semibold">
{t("description")}
</p>
<hr className="divider my-2 border-t border-neutral-content h-[1px]" />
<p>
{link.description ? (
unescapeString(link.description)
) : (
<span className="text-neutral text-sm">
{t("no_description")}
</span>
)}
</p>
{link.tags && link.tags[0] && (
<>
<p className="text-neutral text-lg mt-3 font-semibold">
{t("tags")}
</p>
<hr className="divider my-2 border-t border-neutral-content h-[1px]" />
<div className="flex gap-3 items-center flex-wrap mt-2 truncate relative">
<div className="flex gap-1 items-center flex-wrap">
{link.tags.map((e, i) => (
<Link
href={"/tags/" + e.id}
key={i}
onClick={(e) => {
e.stopPropagation();
}}
className="btn btn-xs btn-ghost truncate max-w-[19rem]"
>
#{e.name}
</Link>
))}
</div>
</div>
</>
)}
</div>
)}
<LinkActions
link={link}
collection={collection}
position="top-[10.75rem] right-3"
toggleShowInfo={() => setShowInfo(!showInfo)}
linkInfo={showInfo}
flipDropdown={flipDropdown}
/>
</div>

View File

@ -80,6 +80,8 @@ export default function LinkCardCompact({
const permissions = usePermissions(collection?.id as number);
const [showInfo, setShowInfo] = useState(false);
const selectedStyle = selectedLinks.some(
(selectedLink) => selectedLink.id === link.id
)
@ -94,7 +96,7 @@ export default function LinkCardCompact({
<>
<div
className={`${selectedStyle} border relative items-center flex ${
!isPWA() ? "hover:bg-base-300 p-3" : "py-3"
!showInfo && !isPWA() ? "hover:bg-base-300 p-3" : "py-3"
} duration-200 rounded-lg w-full`}
onClick={() =>
selectable
@ -104,6 +106,20 @@ export default function LinkCardCompact({
: undefined
}
>
{/* {showCheckbox &&
editMode &&
(permissions === true ||
permissions?.canCreate ||
permissions?.canDelete) && (
<input
type="checkbox"
className="checkbox checkbox-primary my-auto mr-2"
checked={selectedLinks.some(
(selectedLink) => selectedLink.id === link.id
)}
onChange={() => handleCheckboxClick(link)}
/>
)} */}
<div
className="flex items-center cursor-pointer w-full"
onClick={() =>
@ -141,6 +157,8 @@ export default function LinkCardCompact({
collection={collection}
position="top-3 right-3"
flipDropdown={flipDropdown}
// toggleShowInfo={() => setShowInfo(!showInfo)}
// linkInfo={showInfo}
/>
</div>
<div

View File

@ -96,7 +96,7 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
link.preview !== "unavailable"
) {
interval = setInterval(async () => {
getLink.mutateAsync({ id: link.id as number });
getLink.mutateAsync(link.id as number);
}, 5000);
}
@ -107,6 +107,8 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
};
}, [isVisible, link.preview]);
const [showInfo, setShowInfo] = useState(false);
const selectedStyle = selectedLinks.some(
(selectedLink) => selectedLink.id === link.id
)
@ -205,6 +207,57 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
</div>
</div>
{showInfo && (
<div className="p-3 absolute z-30 top-0 left-0 right-0 bottom-0 bg-base-200 rounded-2xl fade-in overflow-y-auto">
<div
onClick={() => setShowInfo(!showInfo)}
className=" float-right btn btn-sm outline-none btn-circle btn-ghost z-10"
>
<i className="bi-x text-neutral text-2xl"></i>
</div>
<p className="text-neutral text-lg font-semibold">
{t("description")}
</p>
<hr className="divider my-2 last:hidden border-t border-neutral-content h-[1px]" />
<p>
{link.description ? (
unescapeString(link.description)
) : (
<span className="text-neutral text-sm">
{t("no_description")}
</span>
)}
</p>
{link.tags && link.tags[0] && (
<>
<p className="text-neutral text-lg mt-3 font-semibold">
{t("tags")}
</p>
<hr className="divider my-2 last:hidden border-t border-neutral-content h-[1px]" />
<div className="flex gap-3 items-center flex-wrap mt-2 truncate relative">
<div className="flex gap-1 items-center flex-wrap">
{link.tags.map((e, i) => (
<Link
href={"/tags/" + e.id}
key={i}
onClick={(e) => {
e.stopPropagation();
}}
className="btn btn-xs btn-ghost truncate max-w-[19rem]"
>
#{e.name}
</Link>
))}
</div>
</div>
</>
)}
</div>
)}
<LinkActions
link={link}
collection={collection}
@ -213,6 +266,8 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
? "top-[10.75rem] right-3"
: "top-[.75rem] right-3"
}
toggleShowInfo={() => setShowInfo(!showInfo)}
linkInfo={showInfo}
flipDropdown={flipDropdown}
/>
</div>

View File

@ -32,7 +32,7 @@ export default function Modal({
return (
<Drawer.Root
open={drawerIsOpen}
onClose={() => dismissible && setTimeout(() => toggleModal(), 350)}
onClose={() => dismissible && setTimeout(() => toggleModal(), 100)}
dismissible={dismissible}
>
<Drawer.Portal>

View File

@ -11,7 +11,6 @@ import { dropdownTriggerer } from "@/lib/client/utils";
import { useTranslation } from "next-i18next";
import { useUpdateCollection } from "@/hooks/store/collections";
import { useUser } from "@/hooks/store/user";
import CopyButton from "../CopyButton";
type Props = {
onClose: Function;
@ -134,11 +133,21 @@ export default function EditCollectionSharingModal({
)}
{collection.isPublic ? (
<div>
<div className={permissions === true ? "pl-5" : ""}>
<p className="mb-2">{t("sharable_link_guide")}</p>
<div className="w-full hide-scrollbar overflow-x-auto whitespace-nowrap rounded-md p-2 bg-base-200 border-neutral-content border-solid border flex items-center gap-2 justify-between">
<div
onClick={() => {
try {
navigator.clipboard
.writeText(publicCollectionURL)
.then(() => toast.success(t("copied")));
} catch (err) {
console.log(err);
}
}}
className="w-full hide-scrollbar overflow-x-auto whitespace-nowrap rounded-md p-2 bg-base-200 border-neutral-content border-solid border outline-none hover:border-primary dark:hover:border-primary duration-100 cursor-text"
>
{publicCollectionURL}
<CopyButton text={publicCollectionURL} />
</div>
</div>
) : null}

View File

@ -1,145 +0,0 @@
import React, { useEffect, useState } from "react";
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
import getPublicUserData from "@/lib/client/getPublicUserData";
import { useTranslation } from "next-i18next";
import { useUser } from "@/hooks/store/user";
import { useGetLink } from "@/hooks/store/links";
import Drawer from "../Drawer";
import LinkDetails from "../LinkDetails";
import Link from "next/link";
import usePermissions from "@/hooks/usePermissions";
import { useRouter } from "next/router";
type Props = {
onClose: Function;
onEdit: Function;
link: LinkIncludingShortenedCollectionAndTags;
};
export default function LinkDetailModal({ onClose, onEdit, link }: Props) {
const { t } = useTranslation();
const getLink = useGetLink();
const { data: user = {} } = useUser();
const [collectionOwner, setCollectionOwner] = useState({
id: null as unknown as number,
name: "",
username: "",
image: "",
archiveAsScreenshot: undefined as unknown as boolean,
archiveAsMonolith: undefined as unknown as boolean,
archiveAsPDF: undefined as unknown as boolean,
});
useEffect(() => {
const fetchOwner = async () => {
if (link.collection.ownerId !== user.id) {
const owner = await getPublicUserData(
link.collection.ownerId as number
);
setCollectionOwner(owner);
} else if (link.collection.ownerId === user.id) {
setCollectionOwner({
id: user.id as number,
name: user.name,
username: user.username as string,
image: user.image as string,
archiveAsScreenshot: user.archiveAsScreenshot as boolean,
archiveAsMonolith: user.archiveAsScreenshot as boolean,
archiveAsPDF: user.archiveAsPDF as boolean,
});
}
};
fetchOwner();
}, [link.collection.ownerId]);
const isReady = () => {
return (
link &&
(collectionOwner.archiveAsScreenshot === true
? link.pdf && link.pdf !== "pending"
: true) &&
(collectionOwner.archiveAsMonolith === true
? link.monolith && link.monolith !== "pending"
: true) &&
(collectionOwner.archiveAsPDF === true
? link.pdf && link.pdf !== "pending"
: true) &&
link.readable &&
link.readable !== "pending"
);
};
useEffect(() => {
(async () => {
await getLink.mutateAsync({
id: link.id as number,
});
})();
let interval: any;
if (!isReady()) {
interval = setInterval(async () => {
await getLink.mutateAsync({
id: link.id as number,
});
}, 5000);
} else {
if (interval) {
clearInterval(interval);
}
}
return () => {
if (interval) {
clearInterval(interval);
}
};
}, [link?.monolith]);
const permissions = usePermissions(link.collection.id as number);
const router = useRouter();
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
return (
<Drawer toggleDrawer={onClose} className="sm:h-screen sm:flex relative">
<div
className="bi-x text-xl text-neutral btn btn-sm btn-square btn-ghost hidden sm:block absolute top-3 left-3"
onClick={() => onClose()}
></div>
<Link
href={isPublicRoute ? `/public/links/${link.id}` : `/links/${link.id}`}
target="_blank"
className="bi-box-arrow-up-right text-xl text-neutral btn btn-sm btn-square btn-ghost absolute top-3 right-3 select-none"
></Link>
<div className="sm:m-auto p-10 w-full max-w-xl">
<LinkDetails link={link} />
{permissions === true ||
(permissions?.canUpdate && (
<>
<br />
<br />
<div className="mx-auto text-center">
<div
className="btn btn-sm btn-ghost"
onClick={() => {
onEdit();
onClose();
}}
>
{t("edit_link")}
</div>
</div>
</>
))}
</div>
</Drawer>
);
}

View File

@ -61,7 +61,7 @@ export default function NewLinkModal({ onClose }: Props) {
};
useEffect(() => {
if (router.pathname.startsWith("/collections/") && router.query.id) {
if (router.query.id) {
const currentCollection = collections.find(
(e) => e.id == Number(router.query.id)
);

View File

@ -7,7 +7,6 @@ import { dropdownTriggerer } from "@/lib/client/utils";
import Button from "../ui/Button";
import { useTranslation } from "next-i18next";
import { useAddToken } from "@/hooks/store/tokens";
import CopyButton from "../CopyButton";
type Props = {
onClose: Function;
@ -69,14 +68,21 @@ export default function NewTokenModal({ onClose }: Props) {
<div className="flex flex-col justify-center space-y-4">
<p className="text-xl font-thin">{t("access_token_created")}</p>
<p>{t("token_creation_notice")}</p>
<div className="relative">
<div className="w-full hide-scrollbar overflow-x-auto whitespace-nowrap rounded-md p-2 bg-base-200 border-neutral-content border-solid border flex items-center gap-2 justify-between pr-14">
{newToken}
<div className="absolute right-0 px-2 border-neutral-content border-solid border-r bg-base-200">
<CopyButton text={newToken} />
</div>
</div>
</div>
<TextInput
spellCheck={false}
value={newToken}
onChange={() => {}}
className="w-full"
/>
<button
onClick={() => {
navigator.clipboard.writeText(newToken);
toast.success(t("copied_to_clipboard"));
}}
className="btn btn-primary w-fit mx-auto"
>
{t("copy_to_clipboard")}
</button>
</div>
) : (
<>

View File

@ -96,14 +96,14 @@ export default function PreservedFormatsModal({ onClose, link }: Props) {
useEffect(() => {
(async () => {
await getLink.mutateAsync({ id: link.id as number });
await getLink.mutateAsync(link.id as number);
})();
let interval: any;
if (!isReady()) {
interval = setInterval(async () => {
await getLink.mutateAsync({ id: link.id as number });
await getLink.mutateAsync(link.id as number);
}, 5000);
} else {
if (interval) {
@ -129,7 +129,7 @@ export default function PreservedFormatsModal({ onClose, link }: Props) {
toast.dismiss(load);
if (response.ok) {
await getLink.mutateAsync({ id: link.id as number });
await getLink.mutateAsync(link?.id as number);
toast.success(t("link_being_archived"));
} else toast.error(data.response);

View File

@ -70,7 +70,7 @@ export default function UploadFileModal({ onClose }: Props) {
useEffect(() => {
setOptionsExpanded(false);
if (router.pathname.startsWith("/collections/") && router.query.id) {
if (router.query.id) {
const currentCollection = collections.find(
(e) => e.id == Number(router.query.id)
);

View File

@ -52,9 +52,11 @@ export default function PreservedFormatRow({
};
return (
<div className="flex justify-between items-center rounded-lg p-2 bg-base-200">
<div className="flex justify-between items-center pr-1 border border-neutral-content rounded-md">
<div className="flex gap-2 items-center">
<i className={`${icon} text-2xl text-primary`} />
<div className="bg-primary text-primary-content p-2 rounded-l-md">
<i className={`${icon} text-2xl`} />
</div>
<p>{name}</p>
</div>
@ -62,7 +64,7 @@ export default function PreservedFormatRow({
{downloadable || false ? (
<div
onClick={() => handleDownload()}
className="btn btn-sm btn-square btn-ghost"
className="btn btn-sm btn-square"
>
<i className="bi-cloud-arrow-down text-xl text-neutral" />
</div>
@ -73,9 +75,9 @@ export default function PreservedFormatRow({
isPublic ? "/public" : ""
}/preserved/${link?.id}?format=${format}`}
target="_blank"
className="btn btn-sm btn-square btn-ghost"
className="btn btn-sm btn-square"
>
<i className="bi-box-arrow-up-right text-lg text-neutral" />
<i className="bi-box-arrow-up-right text-xl text-neutral" />
</Link>
</div>
</div>

View File

@ -46,6 +46,13 @@ export default function ReadableView({ link }: Props) {
const router = useRouter();
const getLink = useGetLink();
const { data: collections = [] } = useCollections();
const collection = useMemo(() => {
return collections.find(
(e) => e.id === link.collection.id
) as CollectionIncludingMembersAndLinkCount;
}, [collections, link]);
useEffect(() => {
const fetchLinkContent = async () => {
@ -66,7 +73,7 @@ export default function ReadableView({ link }: Props) {
}, [link]);
useEffect(() => {
if (link) getLink.mutateAsync({ id: link.id as number });
if (link) getLink.mutateAsync(link?.id as number);
let interval: any;
if (
@ -81,10 +88,7 @@ export default function ReadableView({ link }: Props) {
!link?.monolith)
) {
interval = setInterval(
() =>
getLink.mutateAsync({
id: link.id as number,
}),
() => getLink.mutateAsync(link.id as number),
5000
);
} else {
@ -239,6 +243,13 @@ export default function ReadableView({ link }: Props) {
{link?.name ? <p>{unescapeString(link?.description)}</p> : undefined}
</div>
<LinkActions
link={link}
collection={collection}
position="top-3 right-3"
alignToTop
/>
</div>
<div className="flex flex-col gap-5 h-full">

View File

@ -225,21 +225,9 @@ const useDeleteLink = () => {
const useGetLink = () => {
const queryClient = useQueryClient();
const router = useRouter();
return useMutation({
mutationFn: async ({
id,
isPublicRoute = router.pathname.startsWith("/public") ? true : undefined,
}: {
id: number;
isPublicRoute?: boolean;
}) => {
const path = isPublicRoute
? `/api/v1/public/links/${id}`
: `/api/v1/links/${id}`;
const response = await fetch(path);
mutationFn: async (id: number) => {
const response = await fetch(`/api/v1/links/${id}`);
const data = await response.json();
if (!response.ok) throw new Error(data.response);
@ -262,20 +250,7 @@ const useGetLink = () => {
};
});
queryClient.setQueriesData(
{ queryKey: ["publicLinks"] },
(oldData: any) => {
if (!oldData) return undefined;
return {
pages: oldData.pages.map((page: any) =>
page.map((item: any) => (item.id === data.id ? data : item))
),
pageParams: oldData.pageParams,
};
}
);
// queryClient.invalidateQueries({ queryKey: ["publicLinks"] });
queryClient.invalidateQueries({ queryKey: ["publicLinks"] });
},
});
};

View File

@ -1,18 +0,0 @@
import * as Icons from "@phosphor-icons/react";
import { icons as iconData } from "@phosphor-icons/core";
import { IconEntry as CoreEntry } from "@phosphor-icons/core";
interface IconEntry extends CoreEntry {
Icon: Icons.Icon;
}
export const icons: ReadonlyArray<IconEntry> = iconData.map((entry) => ({
...entry,
Icon: Icons[entry.pascal_name as keyof typeof Icons] as Icons.Icon,
}));
if (process.env.NODE_ENV === "development") {
console.log(`${icons.length} icons`);
}
export const iconCount = Intl.NumberFormat("en-US").format(icons.length * 6);

View File

@ -1,6 +1,6 @@
{
"name": "linkwarden",
"version": "v2.8.0",
"version": "v2.7.1",
"main": "index.js",
"repository": "https://github.com/linkwarden/linkwarden.git",
"author": "Daniel31X13 <daniel31x13@gmail.com>",
@ -25,8 +25,6 @@
"@aws-sdk/client-s3": "^3.379.1",
"@headlessui/react": "^1.7.15",
"@mozilla/readability": "^0.4.4",
"@phosphor-icons/core": "^2.1.1",
"@phosphor-icons/react": "^2.1.7",
"@prisma/client": "^4.16.2",
"@stripe/stripe-js": "^1.54.1",
"@tanstack/react-query": "^5.51.15",
@ -52,7 +50,6 @@
"eslint-config-next": "13.4.9",
"formidable": "^3.5.1",
"framer-motion": "^10.16.4",
"fuse.js": "^7.0.0",
"handlebars": "^4.7.8",
"himalaya": "^1.1.0",
"i18next": "^23.11.5",
@ -79,7 +76,7 @@
"socks-proxy-agent": "^8.0.2",
"stripe": "^12.13.0",
"tailwind-merge": "^2.3.0",
"vaul": "^0.9.1",
"vaul": "^0.8.8",
"zustand": "^4.3.8"
},
"devDependencies": {
@ -95,7 +92,7 @@
"postcss": "^8.4.26",
"prettier": "3.1.1",
"prisma": "^4.16.2",
"tailwindcss": "^3.4.10",
"tailwindcss": "^3.3.3",
"ts-node": "^10.9.2",
"typescript": "4.9.4"
}

View File

@ -111,14 +111,14 @@ export default function Dashboard() {
</div>
<div>
<div className="flex justify-evenly flex-col items-start sm:flex-row sm:items-center gap-5 xl:w-full h-full rounded-2xl p-5 bg-base-200">
<div className="flex justify-evenly flex-col xl:flex-row xl:items-center gap-2 xl:w-full h-full rounded-2xl p-8 border border-neutral-content bg-base-200">
<DashboardItem
name={numberOfLinks === 1 ? t("link") : t("links")}
value={numberOfLinks}
icon={"bi-link-45deg"}
/>
<div className="divider m-0"></div>
<div className="divider xl:divider-horizontal"></div>
<DashboardItem
name={
@ -128,11 +128,11 @@ export default function Dashboard() {
icon={"bi-folder"}
/>
<div className="divider m-0"></div>
<div className="divider xl:divider-horizontal"></div>
<DashboardItem
name={tags.length === 1 ? t("tag") : t("tags")}
value={tags.length * numberOfLinks}
value={tags.length}
icon={"bi-hash"}
/>
</div>

View File

@ -1,41 +0,0 @@
import LinkDetails from "@/components/LinkDetails";
import { useGetLink } from "@/hooks/store/links";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import getServerSideProps from "@/lib/client/getServerSideProps";
const Index = () => {
const router = useRouter();
const { id } = router.query;
useState;
const getLink = useGetLink();
useEffect(() => {
getLink.mutate({ id: Number(id) });
}, []);
return (
<div className="flex h-screen py-20">
{getLink.data ? (
<LinkDetails
link={getLink.data}
className="max-w-xl p-5 m-auto w-full"
/>
) : (
<div className="max-w-xl p-5 m-auto w-full flex flex-col items-center gap-5">
<div className="w-20 h-20 skeleton rounded-xl"></div>
<div className="w-full h-10 skeleton rounded-xl"></div>
<div className="w-full h-10 skeleton rounded-xl"></div>
<div className="w-full h-10 skeleton rounded-xl"></div>
<div className="w-full h-10 skeleton rounded-xl"></div>
</div>
)}
</div>
);
};
export default Index;
export { getServerSideProps };

View File

@ -20,7 +20,7 @@ export default function Index() {
useEffect(() => {
const fetchLink = async () => {
if (router.query.id) {
await getLink.mutateAsync({ id: Number(router.query.id) });
await getLink.mutateAsync(Number(router.query.id));
}
};

View File

@ -1,41 +0,0 @@
import LinkDetails from "@/components/LinkDetails";
import { useGetLink } from "@/hooks/store/links";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import getServerSideProps from "@/lib/client/getServerSideProps";
const Index = () => {
const router = useRouter();
const { id } = router.query;
useState;
const getLink = useGetLink();
useEffect(() => {
getLink.mutate({ id: Number(id) });
}, []);
return (
<div className="flex h-screen py-20">
{getLink.data ? (
<LinkDetails
link={getLink.data}
className="max-w-xl p-5 m-auto w-full"
/>
) : (
<div className="max-w-xl p-5 m-auto w-full flex flex-col items-center gap-5">
<div className="w-20 h-20 skeleton rounded-xl"></div>
<div className="w-full h-10 skeleton rounded-xl"></div>
<div className="w-full h-10 skeleton rounded-xl"></div>
<div className="w-full h-10 skeleton rounded-xl"></div>
<div className="w-full h-10 skeleton rounded-xl"></div>
</div>
)}
</div>
);
};
export default Index;
export { getServerSideProps };

View File

@ -6,9 +6,10 @@ import {
} from "@/types/global";
import ReadableView from "@/components/ReadableView";
import getServerSideProps from "@/lib/client/getServerSideProps";
import { useGetLink } from "@/hooks/store/links";
import { useGetLink, useLinks } from "@/hooks/store/links";
export default function Index() {
const { links } = useLinks();
const getLink = useGetLink();
const [link, setLink] = useState<LinkIncludingShortenedCollectionAndTags>();
@ -18,14 +19,18 @@ export default function Index() {
useEffect(() => {
const fetchLink = async () => {
if (router.query.id) {
const get = await getLink.mutateAsync({ id: Number(router.query.id) });
setLink(get);
await getLink.mutateAsync(Number(router.query.id));
}
};
fetchLink();
}, []);
useEffect(() => {
if (links && links[0])
setLink(links.find((e) => e.id === Number(router.query.id)));
}, [links]);
return (
<div className="relative">
{/* <div className="fixed left-1/2 transform -translate-x-1/2 w-fit py-1 px-3 bg-base-200 border border-neutral-content rounded-md">
@ -34,12 +39,6 @@ export default function Index() {
{link && Number(router.query.format) === ArchivedFormat.readability && (
<ReadableView link={link} />
)}
{link && Number(router.query.format) === ArchivedFormat.monolith && (
<iframe
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.monolith}`}
className="w-full h-screen border-none"
></iframe>
)}
{link && Number(router.query.format) === ArchivedFormat.pdf && (
<iframe
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.pdf}`}

View File

@ -361,6 +361,7 @@
"unpin": "Unpin",
"pin_to_dashboard": "Pin to Dashboard",
"show_link_details": "Show Link Details",
"hide_link_details": "Hide Link Details",
"link_pinned": "Link Pinned!",
"link_unpinned": "Link Unpinned!",
"webpage": "Webpage",
@ -370,6 +371,5 @@
"demo_title": "Demo Only",
"demo_desc": "This is only a demo instance of Linkwarden and uploads are disabled.",
"demo_desc_2": "If you want to try out the full version, you can sign up for a free trial at:",
"demo_button": "Login as demo user",
"notes": "Notes"
"demo_button": "Login as demo user"
}

View File

@ -1281,16 +1281,6 @@
resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.1.1.tgz#ab9cd8755d1976e72fc77a00f7655a64efe6cd5d"
integrity sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==
"@phosphor-icons/core@^2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@phosphor-icons/core/-/core-2.1.1.tgz#62a4cfbec9772f1a613a647da214fbb96f3ad39d"
integrity sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ==
"@phosphor-icons/react@^2.1.7":
version "2.1.7"
resolved "https://registry.yarnpkg.com/@phosphor-icons/react/-/react-2.1.7.tgz#b11a4b25849b7e3849970b688d9fe91e5d4fd8d7"
integrity sha512-g2e2eVAn1XG2a+LI09QU3IORLhnFNAFkNbo2iwbX6NOKSLOwvEMmTa7CgOzEbgNWR47z8i8kwjdvYZ5fkGx1mQ==
"@pkgr/utils@^2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03"
@ -3379,7 +3369,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-glob@^3.2.11, fast-glob@^3.2.9:
fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9:
version "3.2.12"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
@ -3390,17 +3380,6 @@ fast-glob@^3.2.11, fast-glob@^3.2.9:
merge2 "^1.3.0"
micromatch "^4.0.4"
fast-glob@^3.3.0:
version "3.3.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
glob-parent "^5.1.2"
merge2 "^1.3.0"
micromatch "^4.0.4"
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@ -3574,11 +3553,6 @@ functions-have-names@^1.2.2:
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
fuse.js@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-7.0.0.tgz#6573c9fcd4c8268e403b4fc7d7131ffcf99a9eb2"
integrity sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==
gauge@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
@ -4279,10 +4253,10 @@ jimp@^0.22.10:
"@jimp/types" "^0.22.10"
regenerator-runtime "^0.13.3"
jiti@^1.21.0:
version "1.21.6"
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268"
integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==
jiti@^1.18.2:
version "1.18.2"
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.18.2.tgz#80c3ef3d486ebf2450d9335122b32d121f2a83cd"
integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==
jose@^4.11.1, jose@^4.11.4, jose@^4.14.1:
version "4.14.4"
@ -5887,20 +5861,20 @@ tailwind-merge@^2.3.0:
dependencies:
"@babel/runtime" "^7.24.1"
tailwindcss@^3.4.10:
version "3.4.10"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.10.tgz#70442d9aeb78758d1f911af29af8255ecdb8ffef"
integrity sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==
tailwindcss@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf"
integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==
dependencies:
"@alloc/quick-lru" "^5.2.0"
arg "^5.0.2"
chokidar "^3.5.3"
didyoumean "^1.2.2"
dlv "^1.1.3"
fast-glob "^3.3.0"
fast-glob "^3.2.12"
glob-parent "^6.0.2"
is-glob "^4.0.3"
jiti "^1.21.0"
jiti "^1.18.2"
lilconfig "^2.1.0"
micromatch "^4.0.5"
normalize-path "^3.0.0"
@ -6265,10 +6239,10 @@ v8-compile-cache-lib@^3.0.1:
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
vaul@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/vaul/-/vaul-0.9.1.tgz#3640198e04636b209b1f907fcf3079bec6ecc66b"
integrity sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw==
vaul@^0.8.8:
version "0.8.8"
resolved "https://registry.yarnpkg.com/vaul/-/vaul-0.8.8.tgz#c5edc041825fdeaddf0a89e326abcc7ac7449a2d"
integrity sha512-Z9K2b90M/LtY/sRyM1yfA8Y4mHC/5WIqhO2u7Byr49r5LQXkLGdVXiehsnjtws9CL+DyknwTuRMJXlCOHTqg/g==
dependencies:
"@radix-ui/react-dialog" "^1.0.4"