added details drawer
This commit is contained in:
parent
a40026040c
commit
c18a5f4162
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -12,22 +12,20 @@ import { useTranslation } from "next-i18next";
|
||||||
import { useUser } from "@/hooks/store/user";
|
import { useUser } from "@/hooks/store/user";
|
||||||
import { useDeleteLink, useUpdateLink } from "@/hooks/store/links";
|
import { useDeleteLink, useUpdateLink } from "@/hooks/store/links";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import LinkDetailModal from "@/components/ModalContent/LinkDetailModal";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
link: LinkIncludingShortenedCollectionAndTags;
|
link: LinkIncludingShortenedCollectionAndTags;
|
||||||
collection: CollectionIncludingMembersAndLinkCount;
|
collection: CollectionIncludingMembersAndLinkCount;
|
||||||
position?: string;
|
position?: string;
|
||||||
toggleShowInfo?: () => void;
|
|
||||||
linkInfo?: boolean;
|
|
||||||
alignToTop?: boolean;
|
alignToTop?: boolean;
|
||||||
flipDropdown?: boolean;
|
flipDropdown?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LinkActions({
|
export default function LinkActions({
|
||||||
link,
|
link,
|
||||||
toggleShowInfo,
|
|
||||||
position,
|
position,
|
||||||
linkInfo,
|
|
||||||
alignToTop,
|
alignToTop,
|
||||||
flipDropdown,
|
flipDropdown,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
@ -36,6 +34,7 @@ export default function LinkActions({
|
||||||
const permissions = usePermissions(link.collection.id as number);
|
const permissions = usePermissions(link.collection.id as number);
|
||||||
|
|
||||||
const [editLinkModal, setEditLinkModal] = useState(false);
|
const [editLinkModal, setEditLinkModal] = useState(false);
|
||||||
|
const [linkDetailModal, setLinkDetailModal] = useState(false);
|
||||||
const [deleteLinkModal, setDeleteLinkModal] = useState(false);
|
const [deleteLinkModal, setDeleteLinkModal] = useState(false);
|
||||||
const [preservedFormatsModal, setPreservedFormatsModal] = useState(false);
|
const [preservedFormatsModal, setPreservedFormatsModal] = useState(false);
|
||||||
|
|
||||||
|
@ -70,119 +69,137 @@ export default function LinkActions({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
{isPublicRoute ? (
|
||||||
className={`dropdown dropdown-left absolute ${
|
|
||||||
position || "top-3 right-3"
|
|
||||||
} ${alignToTop ? "" : "dropdown-end"} z-20`}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
className={`absolute ${position || "top-3 right-3"} ${
|
||||||
role="button"
|
alignToTop ? "" : "dropdown-end"
|
||||||
onMouseDown={dropdownTriggerer}
|
} z-20`}
|
||||||
className="btn btn-ghost btn-sm btn-square text-neutral"
|
onClick={() => setLinkDetailModal(true)}
|
||||||
>
|
>
|
||||||
<i title="More" className="bi-three-dots text-xl" />
|
<div className="btn btn-ghost btn-sm btn-square text-neutral">
|
||||||
|
<i title="More" className="bi-three-dots text-xl" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
) : (
|
||||||
className={`dropdown-content z-[20] menu shadow bg-base-200 border border-neutral-content rounded-box mr-1 ${
|
<div
|
||||||
alignToTop ? "" : "translate-y-10"
|
className={`dropdown dropdown-left absolute ${
|
||||||
}`}
|
position || "top-3 right-3"
|
||||||
|
} ${alignToTop ? "" : "dropdown-end"} z-20`}
|
||||||
>
|
>
|
||||||
<li>
|
<div
|
||||||
<div
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
onMouseDown={dropdownTriggerer}
|
||||||
onClick={() => {
|
className="btn btn-ghost btn-sm btn-square text-neutral"
|
||||||
(document?.activeElement as HTMLElement)?.blur();
|
>
|
||||||
pinLink();
|
<i title="More" className="bi-three-dots text-xl" />
|
||||||
}}
|
</div>
|
||||||
className="whitespace-nowrap"
|
<ul
|
||||||
>
|
className={`dropdown-content z-[20] menu shadow bg-base-200 border border-neutral-content rounded-box mr-1 ${
|
||||||
{link?.pinnedBy && link.pinnedBy[0]
|
alignToTop ? "" : "translate-y-10"
|
||||||
? t("unpin")
|
}`}
|
||||||
: t("pin_to_dashboard")}
|
>
|
||||||
</div>
|
{permissions === true ||
|
||||||
</li>
|
(permissions?.canUpdate && (
|
||||||
{linkInfo !== undefined && toggleShowInfo ? (
|
<li>
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => {
|
||||||
|
(document?.activeElement as HTMLElement)?.blur();
|
||||||
|
pinLink();
|
||||||
|
}}
|
||||||
|
className="whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{link?.pinnedBy && link.pinnedBy[0]
|
||||||
|
? t("unpin")
|
||||||
|
: t("pin_to_dashboard")}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
<li>
|
<li>
|
||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
(document?.activeElement as HTMLElement)?.blur();
|
(document?.activeElement as HTMLElement)?.blur();
|
||||||
toggleShowInfo();
|
setLinkDetailModal(true);
|
||||||
}}
|
}}
|
||||||
className="whitespace-nowrap"
|
className="whitespace-nowrap"
|
||||||
>
|
>
|
||||||
{!linkInfo ? t("show_link_details") : t("hide_link_details")}
|
{t("show_link_details")}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
) : undefined}
|
{permissions === true || permissions?.canUpdate ? (
|
||||||
{permissions === true || permissions?.canUpdate ? (
|
<li>
|
||||||
<li>
|
<div
|
||||||
<div
|
role="button"
|
||||||
role="button"
|
tabIndex={0}
|
||||||
tabIndex={0}
|
onClick={() => {
|
||||||
onClick={() => {
|
(document?.activeElement as HTMLElement)?.blur();
|
||||||
(document?.activeElement as HTMLElement)?.blur();
|
setEditLinkModal(true);
|
||||||
setEditLinkModal(true);
|
}}
|
||||||
}}
|
className="whitespace-nowrap"
|
||||||
className="whitespace-nowrap"
|
>
|
||||||
>
|
{t("edit_link")}
|
||||||
{t("edit_link")}
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
) : undefined}
|
||||||
) : undefined}
|
{link.type === "url" && (
|
||||||
{link.type === "url" && (
|
<li>
|
||||||
<li>
|
<div
|
||||||
<div
|
role="button"
|
||||||
role="button"
|
tabIndex={0}
|
||||||
tabIndex={0}
|
onClick={() => {
|
||||||
onClick={() => {
|
(document?.activeElement as HTMLElement)?.blur();
|
||||||
(document?.activeElement as HTMLElement)?.blur();
|
setPreservedFormatsModal(true);
|
||||||
setPreservedFormatsModal(true);
|
}}
|
||||||
}}
|
className="whitespace-nowrap"
|
||||||
className="whitespace-nowrap"
|
>
|
||||||
>
|
{t("preserved_formats")}
|
||||||
{t("preserved_formats")}
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
)}
|
||||||
)}
|
{permissions === true || permissions?.canDelete ? (
|
||||||
{permissions === true || permissions?.canDelete ? (
|
<li>
|
||||||
<li>
|
<div
|
||||||
<div
|
role="button"
|
||||||
role="button"
|
tabIndex={0}
|
||||||
tabIndex={0}
|
onClick={async (e) => {
|
||||||
onClick={async (e) => {
|
(document?.activeElement as HTMLElement)?.blur();
|
||||||
(document?.activeElement as HTMLElement)?.blur();
|
e.shiftKey
|
||||||
e.shiftKey
|
? async () => {
|
||||||
? async () => {
|
const load = toast.loading(t("deleting"));
|
||||||
const load = toast.loading(t("deleting"));
|
|
||||||
|
|
||||||
await deleteLink.mutateAsync(link.id as number, {
|
await deleteLink.mutateAsync(link.id as number, {
|
||||||
onSettled: (data, error) => {
|
onSettled: (data, error) => {
|
||||||
toast.dismiss(load);
|
toast.dismiss(load);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
} else {
|
} else {
|
||||||
toast.success(t("deleted"));
|
toast.success(t("deleted"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
: setDeleteLinkModal(true);
|
: setDeleteLinkModal(true);
|
||||||
}}
|
}}
|
||||||
className="whitespace-nowrap"
|
className="whitespace-nowrap"
|
||||||
>
|
>
|
||||||
{t("delete")}
|
{t("delete")}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{editLinkModal ? (
|
{editLinkModal ? (
|
||||||
<EditLinkModal
|
<EditLinkModal
|
||||||
|
@ -202,9 +219,13 @@ export default function LinkActions({
|
||||||
link={link}
|
link={link}
|
||||||
/>
|
/>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
{/* {expandedLink ? (
|
{linkDetailModal ? (
|
||||||
<ExpandedLink onClose={() => setExpandedLink(false)} link={link} />
|
<LinkDetailModal
|
||||||
) : undefined} */}
|
onClose={() => setLinkDetailModal(false)}
|
||||||
|
onEdit={() => setEditLinkModal(true)}
|
||||||
|
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: any;
|
let interval: any;
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
|
@ -11,6 +11,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,21 +134,11 @@ export default function EditCollectionSharingModal({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{collection.isPublic ? (
|
{collection.isPublic ? (
|
||||||
<div className={permissions === true ? "pl-5" : ""}>
|
<div>
|
||||||
<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>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -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)
|
||||||
);
|
);
|
||||||
|
|
|
@ -96,14 +96,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: any;
|
let interval: any;
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -129,7 +129,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)
|
||||||
);
|
);
|
||||||
|
|
|
@ -52,11 +52,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>
|
||||||
|
|
||||||
|
@ -64,7 +62,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>
|
||||||
|
@ -75,9 +73,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: any;
|
let interval: any;
|
||||||
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 {
|
||||||
|
@ -243,13 +239,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">
|
||||||
|
|
|
@ -225,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);
|
||||||
|
@ -250,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"] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -79,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": {
|
||||||
|
@ -95,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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,14 +111,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={
|
||||||
|
@ -128,11 +128,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) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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}`}
|
||||||
|
|
|
@ -361,7 +361,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",
|
||||||
|
@ -371,5 +370,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"
|
||||||
}
|
}
|
41
yarn.lock
41
yarn.lock
|
@ -3379,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==
|
||||||
|
@ -3390,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"
|
||||||
|
@ -4268,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"
|
||||||
|
@ -5876,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"
|
||||||
|
@ -6254,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