Merge branch 'dev' into fixes
This commit is contained in:
commit
2b83522eaa
|
@ -0,0 +1,36 @@
|
||||||
|
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;
|
|
@ -14,7 +14,12 @@ export default function dashboardItem({
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 flex flex-col justify-center">
|
<div className="ml-4 flex flex-col justify-center">
|
||||||
<p className="text-neutral text-xs tracking-wider">{name}</p>
|
<p className="text-neutral text-xs tracking-wider">{name}</p>
|
||||||
<p className="font-thin text-5xl text-primary mt-0.5">{value}</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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
import { dropdownTriggerer } from "@/lib/client/utils";
|
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
|
import { resetInfiniteQueryPagination } from "@/hooks/store/links";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setSearchFilter: Function;
|
setSearchFilter: Function;
|
||||||
|
@ -18,6 +20,7 @@ export default function FilterSearchDropdown({
|
||||||
searchFilter,
|
searchFilter,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dropdown dropdown-bottom dropdown-end">
|
<div className="dropdown dropdown-bottom dropdown-end">
|
||||||
|
@ -41,9 +44,10 @@ export default function FilterSearchDropdown({
|
||||||
name="search-filter-checkbox"
|
name="search-filter-checkbox"
|
||||||
className="checkbox checkbox-primary"
|
className="checkbox checkbox-primary"
|
||||||
checked={searchFilter.name}
|
checked={searchFilter.name}
|
||||||
onChange={() =>
|
onChange={() => {
|
||||||
setSearchFilter({ ...searchFilter, name: !searchFilter.name })
|
resetInfiniteQueryPagination(queryClient, ["links"]);
|
||||||
}
|
setSearchFilter({ ...searchFilter, name: !searchFilter.name });
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="label-text whitespace-nowrap">{t("name")}</span>
|
<span className="label-text whitespace-nowrap">{t("name")}</span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -59,9 +63,10 @@ export default function FilterSearchDropdown({
|
||||||
name="search-filter-checkbox"
|
name="search-filter-checkbox"
|
||||||
className="checkbox checkbox-primary"
|
className="checkbox checkbox-primary"
|
||||||
checked={searchFilter.url}
|
checked={searchFilter.url}
|
||||||
onChange={() =>
|
onChange={() => {
|
||||||
setSearchFilter({ ...searchFilter, url: !searchFilter.url })
|
resetInfiniteQueryPagination(queryClient, ["links"]);
|
||||||
}
|
setSearchFilter({ ...searchFilter, url: !searchFilter.url });
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="label-text whitespace-nowrap">{t("link")}</span>
|
<span className="label-text whitespace-nowrap">{t("link")}</span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -77,12 +82,13 @@ export default function FilterSearchDropdown({
|
||||||
name="search-filter-checkbox"
|
name="search-filter-checkbox"
|
||||||
className="checkbox checkbox-primary"
|
className="checkbox checkbox-primary"
|
||||||
checked={searchFilter.description}
|
checked={searchFilter.description}
|
||||||
onChange={() =>
|
onChange={() => {
|
||||||
|
resetInfiniteQueryPagination(queryClient, ["links"]);
|
||||||
setSearchFilter({
|
setSearchFilter({
|
||||||
...searchFilter,
|
...searchFilter,
|
||||||
description: !searchFilter.description,
|
description: !searchFilter.description,
|
||||||
})
|
});
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="label-text whitespace-nowrap">
|
<span className="label-text whitespace-nowrap">
|
||||||
{t("description")}
|
{t("description")}
|
||||||
|
@ -100,9 +106,10 @@ export default function FilterSearchDropdown({
|
||||||
name="search-filter-checkbox"
|
name="search-filter-checkbox"
|
||||||
className="checkbox checkbox-primary"
|
className="checkbox checkbox-primary"
|
||||||
checked={searchFilter.tags}
|
checked={searchFilter.tags}
|
||||||
onChange={() =>
|
onChange={() => {
|
||||||
setSearchFilter({ ...searchFilter, tags: !searchFilter.tags })
|
resetInfiniteQueryPagination(queryClient, ["links"]);
|
||||||
}
|
setSearchFilter({ ...searchFilter, tags: !searchFilter.tags });
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="label-text whitespace-nowrap">{t("tags")}</span>
|
<span className="label-text whitespace-nowrap">{t("tags")}</span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -118,12 +125,13 @@ export default function FilterSearchDropdown({
|
||||||
name="search-filter-checkbox"
|
name="search-filter-checkbox"
|
||||||
className="checkbox checkbox-primary"
|
className="checkbox checkbox-primary"
|
||||||
checked={searchFilter.textContent}
|
checked={searchFilter.textContent}
|
||||||
onChange={() =>
|
onChange={() => {
|
||||||
|
resetInfiniteQueryPagination(queryClient, ["links"]);
|
||||||
setSearchFilter({
|
setSearchFilter({
|
||||||
...searchFilter,
|
...searchFilter,
|
||||||
textContent: !searchFilter.textContent,
|
textContent: !searchFilter.textContent,
|
||||||
})
|
});
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="label-text whitespace-nowrap">
|
<span className="label-text whitespace-nowrap">
|
||||||
{t("full_content")}
|
{t("full_content")}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
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;
|
|
@ -0,0 +1,302 @@
|
||||||
|
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="rounded-lg p-2 bg-base-200 flex justify-between items-center gap-2">
|
||||||
|
<Link
|
||||||
|
href={link.url}
|
||||||
|
title={link.url}
|
||||||
|
target="_blank"
|
||||||
|
className="truncate"
|
||||||
|
>
|
||||||
|
{link.url}
|
||||||
|
</Link>
|
||||||
|
<CopyButton text={link.url} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<p className="text-sm mb-2 text-neutral">{t("collection")}</p>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
isPublicRoute
|
||||||
|
? `/public/collections/${link.collection.id}`
|
||||||
|
: `/collections/${link.collection.id}`
|
||||||
|
}
|
||||||
|
className="rounded-lg p-2 bg-base-200 flex justify-between items-center gap-2"
|
||||||
|
>
|
||||||
|
<p className="truncate">{link.collection.name}</p>
|
||||||
|
<i
|
||||||
|
className="bi-folder-fill text-xl"
|
||||||
|
style={{ color: link.collection.color }}
|
||||||
|
></i>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -202,9 +202,6 @@ export default function LinkActions({
|
||||||
link={link}
|
link={link}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* {expandedLink ? (
|
|
||||||
<ExpandedLink onClose={() => setExpandedLink(false)} link={link} />
|
|
||||||
) : undefined} */}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { useTranslation } from "next-i18next";
|
||||||
import { useCollections } from "@/hooks/store/collections";
|
import { useCollections } from "@/hooks/store/collections";
|
||||||
import { useUser } from "@/hooks/store/user";
|
import { useUser } from "@/hooks/store/user";
|
||||||
import { useGetLink, useLinks } from "@/hooks/store/links";
|
import { useGetLink, useLinks } from "@/hooks/store/links";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
link: LinkIncludingShortenedCollectionAndTags;
|
link: LinkIncludingShortenedCollectionAndTags;
|
||||||
|
@ -90,6 +91,10 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
|
||||||
const isVisible = useOnScreen(ref);
|
const isVisible = useOnScreen(ref);
|
||||||
const permissions = usePermissions(collection?.id as number);
|
const permissions = usePermissions(collection?.id as number);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
let isPublic = router.pathname.startsWith("/public") ? true : undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let interval: NodeJS.Timeout | null = null;
|
let interval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
@ -99,7 +104,7 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
|
||||||
link.preview !== "unavailable"
|
link.preview !== "unavailable"
|
||||||
) {
|
) {
|
||||||
interval = setInterval(async () => {
|
interval = setInterval(async () => {
|
||||||
getLink.mutateAsync(link.id as number);
|
getLink.mutateAsync({ id: link.id as number, isPublicRoute: isPublic });
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,8 +115,6 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
|
||||||
};
|
};
|
||||||
}, [isVisible, link.preview]);
|
}, [isVisible, link.preview]);
|
||||||
|
|
||||||
const [showInfo, setShowInfo] = useState(false);
|
|
||||||
|
|
||||||
const selectedStyle = selectedLinks.some(
|
const selectedStyle = selectedLinks.some(
|
||||||
(selectedLink) => selectedLink.id === link.id
|
(selectedLink) => selectedLink.id === link.id
|
||||||
)
|
)
|
||||||
|
@ -196,63 +199,10 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
|
||||||
</div>
|
</div>
|
||||||
</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
|
<LinkActions
|
||||||
link={link}
|
link={link}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
position="top-[10.75rem] right-3"
|
position="top-[10.75rem] right-3"
|
||||||
toggleShowInfo={() => setShowInfo(!showInfo)}
|
|
||||||
linkInfo={showInfo}
|
|
||||||
flipDropdown={flipDropdown}
|
flipDropdown={flipDropdown}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -80,8 +80,6 @@ export default function LinkCardCompact({
|
||||||
|
|
||||||
const permissions = usePermissions(collection?.id as number);
|
const permissions = usePermissions(collection?.id as number);
|
||||||
|
|
||||||
const [showInfo, setShowInfo] = useState(false);
|
|
||||||
|
|
||||||
const selectedStyle = selectedLinks.some(
|
const selectedStyle = selectedLinks.some(
|
||||||
(selectedLink) => selectedLink.id === link.id
|
(selectedLink) => selectedLink.id === link.id
|
||||||
)
|
)
|
||||||
|
@ -96,7 +94,7 @@ export default function LinkCardCompact({
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`${selectedStyle} border relative items-center flex ${
|
className={`${selectedStyle} border relative items-center flex ${
|
||||||
!showInfo && !isPWA() ? "hover:bg-base-300 p-3" : "py-3"
|
!isPWA() ? "hover:bg-base-300 p-3" : "py-3"
|
||||||
} duration-200 rounded-lg w-full`}
|
} duration-200 rounded-lg w-full`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
selectable
|
selectable
|
||||||
|
@ -106,20 +104,6 @@ export default function LinkCardCompact({
|
||||||
: undefined
|
: 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
|
<div
|
||||||
className="flex items-center cursor-pointer w-full"
|
className="flex items-center cursor-pointer w-full"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
@ -157,8 +141,6 @@ export default function LinkCardCompact({
|
||||||
collection={collection}
|
collection={collection}
|
||||||
position="top-3 right-3"
|
position="top-3 right-3"
|
||||||
flipDropdown={flipDropdown}
|
flipDropdown={flipDropdown}
|
||||||
// toggleShowInfo={() => setShowInfo(!showInfo)}
|
|
||||||
// linkInfo={showInfo}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -96,7 +96,7 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
|
||||||
link.preview !== "unavailable"
|
link.preview !== "unavailable"
|
||||||
) {
|
) {
|
||||||
interval = setInterval(async () => {
|
interval = setInterval(async () => {
|
||||||
getLink.mutateAsync(link.id as number);
|
getLink.mutateAsync({ id: link.id as number });
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,8 +107,6 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
|
||||||
};
|
};
|
||||||
}, [isVisible, link.preview]);
|
}, [isVisible, link.preview]);
|
||||||
|
|
||||||
const [showInfo, setShowInfo] = useState(false);
|
|
||||||
|
|
||||||
const selectedStyle = selectedLinks.some(
|
const selectedStyle = selectedLinks.some(
|
||||||
(selectedLink) => selectedLink.id === link.id
|
(selectedLink) => selectedLink.id === link.id
|
||||||
)
|
)
|
||||||
|
@ -207,57 +205,6 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
|
||||||
</div>
|
</div>
|
||||||
</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
|
<LinkActions
|
||||||
link={link}
|
link={link}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
|
@ -266,8 +213,6 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
|
||||||
? "top-[10.75rem] right-3"
|
? "top-[10.75rem] right-3"
|
||||||
: "top-[.75rem] right-3"
|
: "top-[.75rem] right-3"
|
||||||
}
|
}
|
||||||
toggleShowInfo={() => setShowInfo(!showInfo)}
|
|
||||||
linkInfo={showInfo}
|
|
||||||
flipDropdown={flipDropdown}
|
flipDropdown={flipDropdown}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -32,7 +32,7 @@ export default function Modal({
|
||||||
return (
|
return (
|
||||||
<Drawer.Root
|
<Drawer.Root
|
||||||
open={drawerIsOpen}
|
open={drawerIsOpen}
|
||||||
onClose={() => dismissible && setTimeout(() => toggleModal(), 100)}
|
onClose={() => dismissible && setTimeout(() => toggleModal(), 350)}
|
||||||
dismissible={dismissible}
|
dismissible={dismissible}
|
||||||
>
|
>
|
||||||
<Drawer.Portal>
|
<Drawer.Portal>
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { dropdownTriggerer } from "@/lib/client/utils";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import { useUpdateCollection } from "@/hooks/store/collections";
|
import { useUpdateCollection } from "@/hooks/store/collections";
|
||||||
import { useUser } from "@/hooks/store/user";
|
import { useUser } from "@/hooks/store/user";
|
||||||
|
import CopyButton from "../CopyButton";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: Function;
|
onClose: Function;
|
||||||
|
@ -133,19 +134,9 @@ export default function EditCollectionSharingModal({
|
||||||
{collection.isPublic && (
|
{collection.isPublic && (
|
||||||
<div className={permissions === true ? "pl-5" : ""}>
|
<div className={permissions === true ? "pl-5" : ""}>
|
||||||
<p className="mb-2">{t("sharable_link_guide")}</p>
|
<p className="mb-2">{t("sharable_link_guide")}</p>
|
||||||
<div
|
<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">
|
||||||
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}
|
{publicCollectionURL}
|
||||||
|
<CopyButton text={publicCollectionURL} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
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 sm:w-5/6">
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -61,7 +61,7 @@ export default function NewLinkModal({ onClose }: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.query.id) {
|
if (router.pathname.startsWith("/collections/") && router.query.id) {
|
||||||
const currentCollection = collections.find(
|
const currentCollection = collections.find(
|
||||||
(e) => e.id == Number(router.query.id)
|
(e) => e.id == Number(router.query.id)
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { dropdownTriggerer } from "@/lib/client/utils";
|
||||||
import Button from "../ui/Button";
|
import Button from "../ui/Button";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import { useAddToken } from "@/hooks/store/tokens";
|
import { useAddToken } from "@/hooks/store/tokens";
|
||||||
|
import CopyButton from "../CopyButton";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: Function;
|
onClose: Function;
|
||||||
|
@ -68,21 +69,14 @@ export default function NewTokenModal({ onClose }: Props) {
|
||||||
<div className="flex flex-col justify-center space-y-4">
|
<div className="flex flex-col justify-center space-y-4">
|
||||||
<p className="text-xl font-thin">{t("access_token_created")}</p>
|
<p className="text-xl font-thin">{t("access_token_created")}</p>
|
||||||
<p>{t("token_creation_notice")}</p>
|
<p>{t("token_creation_notice")}</p>
|
||||||
<TextInput
|
<div className="relative">
|
||||||
spellCheck={false}
|
<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">
|
||||||
value={newToken}
|
{newToken}
|
||||||
onChange={() => {}}
|
<div className="absolute right-0 px-2 border-neutral-content border-solid border-r bg-base-200">
|
||||||
className="w-full"
|
<CopyButton text={newToken} />
|
||||||
/>
|
</div>
|
||||||
<button
|
</div>
|
||||||
onClick={() => {
|
</div>
|
||||||
navigator.clipboard.writeText(newToken);
|
|
||||||
toast.success(t("copied_to_clipboard"));
|
|
||||||
}}
|
|
||||||
className="btn btn-primary w-fit mx-auto"
|
|
||||||
>
|
|
||||||
{t("copy_to_clipboard")}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -91,14 +91,14 @@ export default function PreservedFormatsModal({ onClose, link }: Props) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
await getLink.mutateAsync(link.id as number);
|
await getLink.mutateAsync({ id: link.id as number });
|
||||||
})();
|
})();
|
||||||
|
|
||||||
let interval: NodeJS.Timeout | null = null;
|
let interval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
if (!isReady()) {
|
if (!isReady()) {
|
||||||
interval = setInterval(async () => {
|
interval = setInterval(async () => {
|
||||||
await getLink.mutateAsync(link.id as number);
|
await getLink.mutateAsync({ id: link.id as number });
|
||||||
}, 5000);
|
}, 5000);
|
||||||
} else {
|
} else {
|
||||||
if (interval) {
|
if (interval) {
|
||||||
|
@ -124,7 +124,7 @@ export default function PreservedFormatsModal({ onClose, link }: Props) {
|
||||||
toast.dismiss(load);
|
toast.dismiss(load);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
await getLink.mutateAsync(link?.id as number);
|
await getLink.mutateAsync({ id: link.id as number });
|
||||||
|
|
||||||
toast.success(t("link_being_archived"));
|
toast.success(t("link_being_archived"));
|
||||||
} else toast.error(data.response);
|
} else toast.error(data.response);
|
||||||
|
|
|
@ -70,7 +70,7 @@ export default function UploadFileModal({ onClose }: Props) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOptionsExpanded(false);
|
setOptionsExpanded(false);
|
||||||
if (router.query.id) {
|
if (router.pathname.startsWith("/collections/") && router.query.id) {
|
||||||
const currentCollection = collections.find(
|
const currentCollection = collections.find(
|
||||||
(e) => e.id == Number(router.query.id)
|
(e) => e.id == Number(router.query.id)
|
||||||
);
|
);
|
||||||
|
|
|
@ -49,11 +49,9 @@ export default function PreservedFormatRow({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between items-center pr-1 border border-neutral-content rounded-md">
|
<div className="flex justify-between items-center rounded-lg p-2 bg-base-200">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<div className="bg-primary text-primary-content p-2 rounded-l-md">
|
<i className={`${icon} text-2xl text-primary`} />
|
||||||
<i className={`${icon} text-2xl`} />
|
|
||||||
</div>
|
|
||||||
<p>{name}</p>
|
<p>{name}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -61,7 +59,7 @@ export default function PreservedFormatRow({
|
||||||
{downloadable || false ? (
|
{downloadable || false ? (
|
||||||
<div
|
<div
|
||||||
onClick={() => handleDownload()}
|
onClick={() => handleDownload()}
|
||||||
className="btn btn-sm btn-square"
|
className="btn btn-sm btn-square btn-ghost"
|
||||||
>
|
>
|
||||||
<i className="bi-cloud-arrow-down text-xl text-neutral" />
|
<i className="bi-cloud-arrow-down text-xl text-neutral" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -72,9 +70,9 @@ export default function PreservedFormatRow({
|
||||||
isPublic ? "/public" : ""
|
isPublic ? "/public" : ""
|
||||||
}/preserved/${link?.id}?format=${format}`}
|
}/preserved/${link?.id}?format=${format}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="btn btn-sm btn-square"
|
className="btn btn-sm btn-square btn-ghost"
|
||||||
>
|
>
|
||||||
<i className="bi-box-arrow-up-right text-xl text-neutral" />
|
<i className="bi-box-arrow-up-right text-lg text-neutral" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -46,13 +46,6 @@ export default function ReadableView({ link }: Props) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const getLink = useGetLink();
|
const getLink = useGetLink();
|
||||||
const { data: collections = [] } = useCollections();
|
|
||||||
|
|
||||||
const collection = useMemo(() => {
|
|
||||||
return collections.find(
|
|
||||||
(e) => e.id === link.collection.id
|
|
||||||
) as CollectionIncludingMembersAndLinkCount;
|
|
||||||
}, [collections, link]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchLinkContent = async () => {
|
const fetchLinkContent = async () => {
|
||||||
|
@ -73,7 +66,7 @@ export default function ReadableView({ link }: Props) {
|
||||||
}, [link]);
|
}, [link]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (link) getLink.mutateAsync(link?.id as number);
|
if (link) getLink.mutateAsync({ id: link.id as number });
|
||||||
|
|
||||||
let interval: NodeJS.Timeout | null = null;
|
let interval: NodeJS.Timeout | null = null;
|
||||||
if (
|
if (
|
||||||
|
@ -88,7 +81,10 @@ export default function ReadableView({ link }: Props) {
|
||||||
!link?.monolith)
|
!link?.monolith)
|
||||||
) {
|
) {
|
||||||
interval = setInterval(
|
interval = setInterval(
|
||||||
() => getLink.mutateAsync(link.id as number),
|
() =>
|
||||||
|
getLink.mutateAsync({
|
||||||
|
id: link.id as number,
|
||||||
|
}),
|
||||||
5000
|
5000
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -242,13 +238,6 @@ export default function ReadableView({ link }: Props) {
|
||||||
|
|
||||||
{link?.name ? <p>{unescapeString(link?.description)}</p> : undefined}
|
{link?.name ? <p>{unescapeString(link?.description)}</p> : undefined}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<LinkActions
|
|
||||||
link={link}
|
|
||||||
collection={collection}
|
|
||||||
position="top-3 right-3"
|
|
||||||
alignToTop
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-5 h-full">
|
<div className="flex flex-col gap-5 h-full">
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { Sort } from "@/types/global";
|
||||||
import { dropdownTriggerer } from "@/lib/client/utils";
|
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||||
import { TFunction } from "i18next";
|
import { TFunction } from "i18next";
|
||||||
import useLocalSettingsStore from "@/store/localSettings";
|
import useLocalSettingsStore from "@/store/localSettings";
|
||||||
|
import { resetInfiniteQueryPagination } from "@/hooks/store/links";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
sortBy: Sort;
|
sortBy: Sort;
|
||||||
|
@ -12,6 +14,7 @@ type Props = {
|
||||||
|
|
||||||
export default function SortDropdown({ sortBy, setSort, t }: Props) {
|
export default function SortDropdown({ sortBy, setSort, t }: Props) {
|
||||||
const { updateSettings } = useLocalSettingsStore();
|
const { updateSettings } = useLocalSettingsStore();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateSettings({ sortBy });
|
updateSettings({ sortBy });
|
||||||
|
@ -39,7 +42,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
|
||||||
name="sort-radio"
|
name="sort-radio"
|
||||||
className="radio checked:bg-primary"
|
className="radio checked:bg-primary"
|
||||||
checked={sortBy === Sort.DateNewestFirst}
|
checked={sortBy === Sort.DateNewestFirst}
|
||||||
onChange={() => setSort(Sort.DateNewestFirst)}
|
onChange={() => {
|
||||||
|
resetInfiniteQueryPagination(queryClient, ["links"]);
|
||||||
|
setSort(Sort.DateNewestFirst);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="label-text whitespace-nowrap">
|
<span className="label-text whitespace-nowrap">
|
||||||
{t("date_newest_first")}
|
{t("date_newest_first")}
|
||||||
|
@ -57,7 +63,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
|
||||||
name="sort-radio"
|
name="sort-radio"
|
||||||
className="radio checked:bg-primary"
|
className="radio checked:bg-primary"
|
||||||
checked={sortBy === Sort.DateOldestFirst}
|
checked={sortBy === Sort.DateOldestFirst}
|
||||||
onChange={() => setSort(Sort.DateOldestFirst)}
|
onChange={() => {
|
||||||
|
resetInfiniteQueryPagination(queryClient, ["links"]);
|
||||||
|
setSort(Sort.DateOldestFirst);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="label-text whitespace-nowrap">
|
<span className="label-text whitespace-nowrap">
|
||||||
{t("date_oldest_first")}
|
{t("date_oldest_first")}
|
||||||
|
@ -75,7 +84,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
|
||||||
name="sort-radio"
|
name="sort-radio"
|
||||||
className="radio checked:bg-primary"
|
className="radio checked:bg-primary"
|
||||||
checked={sortBy === Sort.NameAZ}
|
checked={sortBy === Sort.NameAZ}
|
||||||
onChange={() => setSort(Sort.NameAZ)}
|
onChange={() => {
|
||||||
|
resetInfiniteQueryPagination(queryClient, ["links"]);
|
||||||
|
setSort(Sort.NameAZ);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="label-text whitespace-nowrap">{t("name_az")}</span>
|
<span className="label-text whitespace-nowrap">{t("name_az")}</span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -91,7 +103,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
|
||||||
name="sort-radio"
|
name="sort-radio"
|
||||||
className="radio checked:bg-primary"
|
className="radio checked:bg-primary"
|
||||||
checked={sortBy === Sort.NameZA}
|
checked={sortBy === Sort.NameZA}
|
||||||
onChange={() => setSort(Sort.NameZA)}
|
onChange={() => {
|
||||||
|
resetInfiniteQueryPagination(queryClient, ["links"]);
|
||||||
|
setSort(Sort.NameZA);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="label-text whitespace-nowrap">{t("name_za")}</span>
|
<span className="label-text whitespace-nowrap">{t("name_za")}</span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -107,7 +122,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
|
||||||
name="sort-radio"
|
name="sort-radio"
|
||||||
className="radio checked:bg-primary"
|
className="radio checked:bg-primary"
|
||||||
checked={sortBy === Sort.DescriptionAZ}
|
checked={sortBy === Sort.DescriptionAZ}
|
||||||
onChange={() => setSort(Sort.DescriptionAZ)}
|
onChange={() => {
|
||||||
|
resetInfiniteQueryPagination(queryClient, ["links"]);
|
||||||
|
setSort(Sort.DescriptionAZ);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="label-text whitespace-nowrap">
|
<span className="label-text whitespace-nowrap">
|
||||||
{t("description_az")}
|
{t("description_az")}
|
||||||
|
@ -125,7 +143,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
|
||||||
name="sort-radio"
|
name="sort-radio"
|
||||||
className="radio checked:bg-primary"
|
className="radio checked:bg-primary"
|
||||||
checked={sortBy === Sort.DescriptionZA}
|
checked={sortBy === Sort.DescriptionZA}
|
||||||
onChange={() => setSort(Sort.DescriptionZA)}
|
onChange={() => {
|
||||||
|
resetInfiniteQueryPagination(queryClient, ["links"]);
|
||||||
|
setSort(Sort.DescriptionZA);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="label-text whitespace-nowrap">
|
<span className="label-text whitespace-nowrap">
|
||||||
{t("description_za")}
|
{t("description_za")}
|
||||||
|
|
|
@ -159,20 +159,23 @@ const useUpdateLink = () => {
|
||||||
return data.response;
|
return data.response;
|
||||||
},
|
},
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
queryClient.setQueryData(["dashboardData"], (oldData: any) => {
|
// queryClient.setQueryData(["dashboardData"], (oldData: any) => {
|
||||||
if (!oldData) return undefined;
|
// if (!oldData) return undefined;
|
||||||
return oldData.map((e: any) => (e.id === data.id ? data : e));
|
// return oldData.map((e: any) => (e.id === data.id ? data : e));
|
||||||
});
|
// });
|
||||||
|
|
||||||
queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) => {
|
// queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) => {
|
||||||
if (!oldData) return undefined;
|
// if (!oldData) return undefined;
|
||||||
return {
|
// return {
|
||||||
pages: oldData.pages.map((page: any) =>
|
// pages: oldData.pages.map((page: any) =>
|
||||||
page.map((item: any) => (item.id === data.id ? data : item))
|
// page.map((item: any) => (item.id === data.id ? data : item))
|
||||||
),
|
// ),
|
||||||
pageParams: oldData.pageParams,
|
// pageParams: oldData.pageParams,
|
||||||
};
|
// };
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["links"] }); // Temporary workaround
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["dashboardData"] }); // Temporary workaround
|
||||||
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["collections"] });
|
queryClient.invalidateQueries({ queryKey: ["collections"] });
|
||||||
queryClient.invalidateQueries({ queryKey: ["tags"] });
|
queryClient.invalidateQueries({ queryKey: ["tags"] });
|
||||||
|
@ -222,9 +225,21 @@ const useDeleteLink = () => {
|
||||||
const useGetLink = () => {
|
const useGetLink = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (id: number) => {
|
mutationFn: async ({
|
||||||
const response = await fetch(`/api/v1/links/${id}`);
|
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);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (!response.ok) throw new Error(data.response);
|
if (!response.ok) throw new Error(data.response);
|
||||||
|
@ -247,7 +262,20 @@ const useGetLink = () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["publicLinks"] });
|
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"] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -398,14 +426,13 @@ const useBulkEditLinks = () => {
|
||||||
return data.response;
|
return data.response;
|
||||||
},
|
},
|
||||||
onSuccess: (data, { links, newData, removePreviousTags }) => {
|
onSuccess: (data, { links, newData, removePreviousTags }) => {
|
||||||
queryClient.setQueryData(["dashboardData"], (oldData: any) => {
|
// TODO: Fix these
|
||||||
if (!oldData) return undefined;
|
// queryClient.setQueryData(["dashboardData"], (oldData: any) => {
|
||||||
return oldData.map((e: any) =>
|
// if (!oldData) return undefined;
|
||||||
data.find((d: any) => d.id === e.id) ? data : e
|
// return oldData.map((e: any) =>
|
||||||
);
|
// data.find((d: any) => d.id === e.id) ? data : e
|
||||||
});
|
// );
|
||||||
|
// });
|
||||||
// TODO: Fix this
|
|
||||||
// queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) => {
|
// queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) => {
|
||||||
// if (!oldData) return undefined;
|
// if (!oldData) return undefined;
|
||||||
// return {
|
// return {
|
||||||
|
@ -417,6 +444,7 @@ const useBulkEditLinks = () => {
|
||||||
// };
|
// };
|
||||||
// });
|
// });
|
||||||
queryClient.invalidateQueries({ queryKey: ["links"] }); // Temporary workaround
|
queryClient.invalidateQueries({ queryKey: ["links"] }); // Temporary workaround
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["dashboardData"] }); // Temporary workaround
|
||||||
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["collections"] });
|
queryClient.invalidateQueries({ queryKey: ["collections"] });
|
||||||
queryClient.invalidateQueries({ queryKey: ["tags"] });
|
queryClient.invalidateQueries({ queryKey: ["tags"] });
|
||||||
|
@ -425,6 +453,22 @@ const useBulkEditLinks = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetInfiniteQueryPagination = async (
|
||||||
|
queryClient: any,
|
||||||
|
queryKey: any
|
||||||
|
) => {
|
||||||
|
queryClient.setQueriesData({ queryKey }, (oldData: any) => {
|
||||||
|
if (!oldData) return undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
pages: oldData.pages.slice(0, 1),
|
||||||
|
pageParams: oldData.pageParams.slice(0, 1),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryClient.invalidateQueries(queryKey);
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
useLinks,
|
useLinks,
|
||||||
useAddLink,
|
useAddLink,
|
||||||
|
@ -434,4 +478,5 @@ export {
|
||||||
useUploadFile,
|
useUploadFile,
|
||||||
useGetLink,
|
useGetLink,
|
||||||
useBulkEditLinks,
|
useBulkEditLinks,
|
||||||
|
resetInfiniteQueryPagination,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
const getPublicCollectionData = async (collectionId: number) => {
|
||||||
import { Dispatch, SetStateAction } from "react";
|
|
||||||
|
|
||||||
const getPublicCollectionData = async (
|
|
||||||
collectionId: number,
|
|
||||||
setData: Dispatch<
|
|
||||||
SetStateAction<CollectionIncludingMembersAndLinkCount | undefined>
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
const res = await fetch("/api/v1/public/collections/" + collectionId);
|
const res = await fetch("/api/v1/public/collections/" + collectionId);
|
||||||
|
|
||||||
if (res.status === 400)
|
if (res.status === 400)
|
||||||
|
@ -14,8 +6,6 @@ const getPublicCollectionData = async (
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
setData(data.response);
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
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);
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "linkwarden",
|
"name": "linkwarden",
|
||||||
"version": "v2.7.0",
|
"version": "v2.8.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": "https://github.com/linkwarden/linkwarden.git",
|
"repository": "https://github.com/linkwarden/linkwarden.git",
|
||||||
"author": "Daniel31X13 <daniel31x13@gmail.com>",
|
"author": "Daniel31X13 <daniel31x13@gmail.com>",
|
||||||
|
@ -25,6 +25,8 @@
|
||||||
"@aws-sdk/client-s3": "^3.379.1",
|
"@aws-sdk/client-s3": "^3.379.1",
|
||||||
"@headlessui/react": "^1.7.15",
|
"@headlessui/react": "^1.7.15",
|
||||||
"@mozilla/readability": "^0.4.4",
|
"@mozilla/readability": "^0.4.4",
|
||||||
|
"@phosphor-icons/core": "^2.1.1",
|
||||||
|
"@phosphor-icons/react": "^2.1.7",
|
||||||
"@prisma/client": "^4.16.2",
|
"@prisma/client": "^4.16.2",
|
||||||
"@stripe/stripe-js": "^1.54.1",
|
"@stripe/stripe-js": "^1.54.1",
|
||||||
"@tanstack/react-query": "^5.51.15",
|
"@tanstack/react-query": "^5.51.15",
|
||||||
|
@ -50,6 +52,7 @@
|
||||||
"eslint-config-next": "13.4.9",
|
"eslint-config-next": "13.4.9",
|
||||||
"formidable": "^3.5.1",
|
"formidable": "^3.5.1",
|
||||||
"framer-motion": "^10.16.4",
|
"framer-motion": "^10.16.4",
|
||||||
|
"fuse.js": "^7.0.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"himalaya": "^1.1.0",
|
"himalaya": "^1.1.0",
|
||||||
"i18next": "^23.11.5",
|
"i18next": "^23.11.5",
|
||||||
|
@ -76,7 +79,7 @@
|
||||||
"socks-proxy-agent": "^8.0.2",
|
"socks-proxy-agent": "^8.0.2",
|
||||||
"stripe": "^12.13.0",
|
"stripe": "^12.13.0",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
"vaul": "^0.8.8",
|
"vaul": "^0.9.1",
|
||||||
"zustand": "^4.3.8"
|
"zustand": "^4.3.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -92,7 +95,7 @@
|
||||||
"postcss": "^8.4.26",
|
"postcss": "^8.4.26",
|
||||||
"prettier": "3.1.1",
|
"prettier": "3.1.1",
|
||||||
"prisma": "^4.16.2",
|
"prisma": "^4.16.2",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.4.10",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "4.9.4"
|
"typescript": "4.9.4"
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,14 +114,14 @@ export default function Dashboard() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<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">
|
<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">
|
||||||
<DashboardItem
|
<DashboardItem
|
||||||
name={numberOfLinks === 1 ? t("link") : t("links")}
|
name={numberOfLinks === 1 ? t("link") : t("links")}
|
||||||
value={numberOfLinks}
|
value={numberOfLinks}
|
||||||
icon={"bi-link-45deg"}
|
icon={"bi-link-45deg"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="divider xl:divider-horizontal"></div>
|
<div className="divider m-0"></div>
|
||||||
|
|
||||||
<DashboardItem
|
<DashboardItem
|
||||||
name={
|
name={
|
||||||
|
@ -131,11 +131,11 @@ export default function Dashboard() {
|
||||||
icon={"bi-folder"}
|
icon={"bi-folder"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="divider xl:divider-horizontal"></div>
|
<div className="divider m-0"></div>
|
||||||
|
|
||||||
<DashboardItem
|
<DashboardItem
|
||||||
name={tags.length === 1 ? t("tag") : t("tags")}
|
name={tags.length === 1 ? t("tag") : t("tags")}
|
||||||
value={tags.length}
|
value={tags.length * numberOfLinks}
|
||||||
icon={"bi-hash"}
|
icon={"bi-hash"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
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">
|
||||||
|
{getLink.data ? (
|
||||||
|
<LinkDetails
|
||||||
|
link={getLink.data}
|
||||||
|
className="mx-auto max-w-2xl p-5 m-auto w-full"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="mx-auto max-w-2xl 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 };
|
|
@ -20,7 +20,7 @@ export default function Index() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchLink = async () => {
|
const fetchLink = async () => {
|
||||||
if (router.query.id) {
|
if (router.query.id) {
|
||||||
await getLink.mutateAsync(Number(router.query.id));
|
await getLink.mutateAsync({ id: Number(router.query.id) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ import EditCollectionSharingModal from "@/components/ModalContent/EditCollection
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import getServerSideProps from "@/lib/client/getServerSideProps";
|
import getServerSideProps from "@/lib/client/getServerSideProps";
|
||||||
import LinkListOptions from "@/components/LinkListOptions";
|
import LinkListOptions from "@/components/LinkListOptions";
|
||||||
import { useCollections } from "@/hooks/store/collections";
|
|
||||||
import { usePublicLinks } from "@/hooks/store/publicLinks";
|
import { usePublicLinks } from "@/hooks/store/publicLinks";
|
||||||
import Links from "@/components/LinkViews/Links";
|
import Links from "@/components/LinkViews/Links";
|
||||||
|
|
||||||
|
@ -29,8 +28,6 @@ export default function PublicCollections() {
|
||||||
|
|
||||||
const { settings } = useLocalSettingsStore();
|
const { settings } = useLocalSettingsStore();
|
||||||
|
|
||||||
const { data: collections = [] } = useCollections();
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [collectionOwner, setCollectionOwner] = useState<
|
const [collectionOwner, setCollectionOwner] = useState<
|
||||||
|
@ -66,25 +63,22 @@ export default function PublicCollections() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.query.id) {
|
if (router.query.id) {
|
||||||
getPublicCollectionData(Number(router.query.id), setCollection).then(
|
getPublicCollectionData(Number(router.query.id)).then((res) => {
|
||||||
(res) => {
|
|
||||||
if (res.status === 400) {
|
if (res.status === 400) {
|
||||||
router.push("/dashboard");
|
router.push("/dashboard");
|
||||||
|
} else {
|
||||||
|
setCollection(res.response);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
}, []);
|
||||||
}
|
|
||||||
}, [collections]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchOwner = async () => {
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
const owner = await getPublicUserData(collection.ownerId as number);
|
getPublicUserData(collection.ownerId as number).then((owner) =>
|
||||||
setCollectionOwner(owner);
|
setCollectionOwner(owner)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
fetchOwner();
|
|
||||||
}, [collection]);
|
}, [collection]);
|
||||||
|
|
||||||
const [editCollectionSharingModal, setEditCollectionSharingModal] =
|
const [editCollectionSharingModal, setEditCollectionSharingModal] =
|
||||||
|
@ -233,9 +227,7 @@ export default function PublicCollections() {
|
||||||
placeholderCount={1}
|
placeholderCount={1}
|
||||||
useData={data}
|
useData={data}
|
||||||
/>
|
/>
|
||||||
{!data.isLoading && links && !links[0] && (
|
{!data.isLoading && links && !links[0] && <p>{t("nothing_found")}</p>}
|
||||||
<p>{t("collection_is_empty")}</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* <p className="text-center text-neutral">
|
{/* <p className="text-center text-neutral">
|
||||||
List created with <span className="text-black">Linkwarden.</span>
|
List created with <span className="text-black">Linkwarden.</span>
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
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">
|
||||||
|
{getLink.data ? (
|
||||||
|
<LinkDetails
|
||||||
|
link={getLink.data}
|
||||||
|
className="mx-auto max-w-2xl p-5 m-auto w-full"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="mx-auto max-w-2xl 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 };
|
|
@ -6,10 +6,9 @@ import {
|
||||||
} from "@/types/global";
|
} from "@/types/global";
|
||||||
import ReadableView from "@/components/ReadableView";
|
import ReadableView from "@/components/ReadableView";
|
||||||
import getServerSideProps from "@/lib/client/getServerSideProps";
|
import getServerSideProps from "@/lib/client/getServerSideProps";
|
||||||
import { useGetLink, useLinks } from "@/hooks/store/links";
|
import { useGetLink } from "@/hooks/store/links";
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const { links } = useLinks();
|
|
||||||
const getLink = useGetLink();
|
const getLink = useGetLink();
|
||||||
|
|
||||||
const [link, setLink] = useState<LinkIncludingShortenedCollectionAndTags>();
|
const [link, setLink] = useState<LinkIncludingShortenedCollectionAndTags>();
|
||||||
|
@ -19,18 +18,14 @@ export default function Index() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchLink = async () => {
|
const fetchLink = async () => {
|
||||||
if (router.query.id) {
|
if (router.query.id) {
|
||||||
await getLink.mutateAsync(Number(router.query.id));
|
const get = await getLink.mutateAsync({ id: Number(router.query.id) });
|
||||||
|
setLink(get);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchLink();
|
fetchLink();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (links && links[0])
|
|
||||||
setLink(links.find((e) => e.id === Number(router.query.id)));
|
|
||||||
}, [links]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<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">
|
{/* <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">
|
||||||
|
@ -39,6 +34,12 @@ export default function Index() {
|
||||||
{link && Number(router.query.format) === ArchivedFormat.readability && (
|
{link && Number(router.query.format) === ArchivedFormat.readability && (
|
||||||
<ReadableView link={link} />
|
<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 && (
|
{link && Number(router.query.format) === ArchivedFormat.pdf && (
|
||||||
<iframe
|
<iframe
|
||||||
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.pdf}`}
|
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.pdf}`}
|
||||||
|
|
|
@ -362,7 +362,6 @@
|
||||||
"unpin": "Unpin",
|
"unpin": "Unpin",
|
||||||
"pin_to_dashboard": "Pin to Dashboard",
|
"pin_to_dashboard": "Pin to Dashboard",
|
||||||
"show_link_details": "Show Link Details",
|
"show_link_details": "Show Link Details",
|
||||||
"hide_link_details": "Hide Link Details",
|
|
||||||
"link_pinned": "Link Pinned!",
|
"link_pinned": "Link Pinned!",
|
||||||
"link_unpinned": "Link Unpinned!",
|
"link_unpinned": "Link Unpinned!",
|
||||||
"webpage": "Webpage",
|
"webpage": "Webpage",
|
||||||
|
@ -372,5 +371,6 @@
|
||||||
"demo_title": "Demo Only",
|
"demo_title": "Demo Only",
|
||||||
"demo_desc": "This is only a demo instance of Linkwarden and uploads are disabled.",
|
"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_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"
|
"demo_button": "Login as demo user",
|
||||||
|
"notes": "Notes"
|
||||||
}
|
}
|
56
yarn.lock
56
yarn.lock
|
@ -1281,6 +1281,16 @@
|
||||||
resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.1.1.tgz#ab9cd8755d1976e72fc77a00f7655a64efe6cd5d"
|
resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.1.1.tgz#ab9cd8755d1976e72fc77a00f7655a64efe6cd5d"
|
||||||
integrity sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==
|
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":
|
"@pkgr/utils@^2.3.1":
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03"
|
resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03"
|
||||||
|
@ -3369,7 +3379,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"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||||
|
|
||||||
fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9:
|
fast-glob@^3.2.11, fast-glob@^3.2.9:
|
||||||
version "3.2.12"
|
version "3.2.12"
|
||||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
|
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
|
||||||
integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
|
integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
|
||||||
|
@ -3380,6 +3390,17 @@ fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9:
|
||||||
merge2 "^1.3.0"
|
merge2 "^1.3.0"
|
||||||
micromatch "^4.0.4"
|
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:
|
fast-json-stable-stringify@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||||
|
@ -3553,6 +3574,11 @@ functions-have-names@^1.2.2:
|
||||||
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
|
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
|
||||||
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
|
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:
|
gauge@^3.0.0:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
|
resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
|
||||||
|
@ -4253,10 +4279,10 @@ jimp@^0.22.10:
|
||||||
"@jimp/types" "^0.22.10"
|
"@jimp/types" "^0.22.10"
|
||||||
regenerator-runtime "^0.13.3"
|
regenerator-runtime "^0.13.3"
|
||||||
|
|
||||||
jiti@^1.18.2:
|
jiti@^1.21.0:
|
||||||
version "1.18.2"
|
version "1.21.6"
|
||||||
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.18.2.tgz#80c3ef3d486ebf2450d9335122b32d121f2a83cd"
|
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268"
|
||||||
integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==
|
integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==
|
||||||
|
|
||||||
jose@^4.11.1, jose@^4.11.4, jose@^4.14.1:
|
jose@^4.11.1, jose@^4.11.4, jose@^4.14.1:
|
||||||
version "4.14.4"
|
version "4.14.4"
|
||||||
|
@ -5861,20 +5887,20 @@ tailwind-merge@^2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.24.1"
|
"@babel/runtime" "^7.24.1"
|
||||||
|
|
||||||
tailwindcss@^3.3.3:
|
tailwindcss@^3.4.10:
|
||||||
version "3.3.3"
|
version "3.4.10"
|
||||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf"
|
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.10.tgz#70442d9aeb78758d1f911af29af8255ecdb8ffef"
|
||||||
integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==
|
integrity sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@alloc/quick-lru" "^5.2.0"
|
"@alloc/quick-lru" "^5.2.0"
|
||||||
arg "^5.0.2"
|
arg "^5.0.2"
|
||||||
chokidar "^3.5.3"
|
chokidar "^3.5.3"
|
||||||
didyoumean "^1.2.2"
|
didyoumean "^1.2.2"
|
||||||
dlv "^1.1.3"
|
dlv "^1.1.3"
|
||||||
fast-glob "^3.2.12"
|
fast-glob "^3.3.0"
|
||||||
glob-parent "^6.0.2"
|
glob-parent "^6.0.2"
|
||||||
is-glob "^4.0.3"
|
is-glob "^4.0.3"
|
||||||
jiti "^1.18.2"
|
jiti "^1.21.0"
|
||||||
lilconfig "^2.1.0"
|
lilconfig "^2.1.0"
|
||||||
micromatch "^4.0.5"
|
micromatch "^4.0.5"
|
||||||
normalize-path "^3.0.0"
|
normalize-path "^3.0.0"
|
||||||
|
@ -6239,10 +6265,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"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
||||||
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
|
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
|
||||||
|
|
||||||
vaul@^0.8.8:
|
vaul@^0.9.1:
|
||||||
version "0.8.8"
|
version "0.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/vaul/-/vaul-0.8.8.tgz#c5edc041825fdeaddf0a89e326abcc7ac7449a2d"
|
resolved "https://registry.yarnpkg.com/vaul/-/vaul-0.9.1.tgz#3640198e04636b209b1f907fcf3079bec6ecc66b"
|
||||||
integrity sha512-Z9K2b90M/LtY/sRyM1yfA8Y4mHC/5WIqhO2u7Byr49r5LQXkLGdVXiehsnjtws9CL+DyknwTuRMJXlCOHTqg/g==
|
integrity sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@radix-ui/react-dialog" "^1.0.4"
|
"@radix-ui/react-dialog" "^1.0.4"
|
||||||
|
|
||||||
|
|
Ŝarĝante…
Reference in New Issue