upload preview functionality
This commit is contained in:
parent
e9072bba51
commit
3de8872f26
|
@ -39,32 +39,10 @@ const IconPopover = ({
|
|||
onClose={() => onClose()}
|
||||
className={clsx(
|
||||
className,
|
||||
"fade-in bg-base-200 border border-neutral-content p-2 h-44 w-[22.5rem] rounded-lg shadow-md"
|
||||
"fade-in bg-base-200 border border-neutral-content p-2 w-[22.5rem] rounded-lg shadow-md"
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-2 h-full w-full">
|
||||
<div className="flex flex-col gap-2 h-full w-fit color-picker">
|
||||
<div
|
||||
className="btn btn-ghost btn-xs"
|
||||
onClick={reset as React.MouseEventHandler<HTMLDivElement>}
|
||||
>
|
||||
{t("reset")}
|
||||
</div>
|
||||
<select
|
||||
className="border border-neutral-content bg-base-100 focus:outline-none focus:border-primary duration-100 w-full rounded-md h-7"
|
||||
value={weight}
|
||||
onChange={(e) => setWeight(e.target.value)}
|
||||
>
|
||||
<option value="regular">{t("regular")}</option>
|
||||
<option value="thin">{t("thin")}</option>
|
||||
<option value="light">{t("light_icon")}</option>
|
||||
<option value="bold">{t("bold")}</option>
|
||||
<option value="fill">{t("fill")}</option>
|
||||
<option value="duotone">{t("duotone")}</option>
|
||||
</select>
|
||||
<HexColorPicker color={color} onChange={(e) => setColor(e)} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<TextInput
|
||||
className="p-2 rounded w-full h-7 text-sm"
|
||||
|
@ -73,7 +51,7 @@ const IconPopover = ({
|
|||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-4 gap-1 w-full overflow-y-auto h-32 border border-neutral-content bg-base-100 rounded-md p-2">
|
||||
<div className="grid grid-cols-6 gap-1 w-full overflow-y-auto h-44 border border-neutral-content bg-base-100 rounded-md p-2">
|
||||
<IconGrid
|
||||
query={query}
|
||||
color={color}
|
||||
|
@ -82,6 +60,79 @@ const IconPopover = ({
|
|||
setIconName={setIconName}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 color-picker w-full">
|
||||
<HexColorPicker color={color} onChange={(e) => setColor(e)} />
|
||||
|
||||
<div className="grid grid-cols-2 gap-1 text-sm">
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
className="radio radio-primary mr-2"
|
||||
value="regular"
|
||||
checked={weight === "regular"}
|
||||
onChange={() => setWeight("regular")}
|
||||
/>
|
||||
{t("regular")}
|
||||
</label>
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
className="radio radio-primary mr-2"
|
||||
value="thin"
|
||||
checked={weight === "thin"}
|
||||
onChange={() => setWeight("thin")}
|
||||
/>
|
||||
{t("thin")}
|
||||
</label>
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
className="radio radio-primary mr-2"
|
||||
value="light"
|
||||
checked={weight === "light"}
|
||||
onChange={() => setWeight("light")}
|
||||
/>
|
||||
{t("light_icon")}
|
||||
</label>
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
className="radio radio-primary mr-2"
|
||||
value="bold"
|
||||
checked={weight === "bold"}
|
||||
onChange={() => setWeight("bold")}
|
||||
/>
|
||||
{t("bold")}
|
||||
</label>
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
className="radio radio-primary mr-2"
|
||||
value="fill"
|
||||
checked={weight === "fill"}
|
||||
onChange={() => setWeight("fill")}
|
||||
/>
|
||||
{t("fill")}
|
||||
</label>
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
className="radio radio-primary mr-2"
|
||||
value="duotone"
|
||||
checked={weight === "duotone"}
|
||||
onChange={() => setWeight("duotone")}
|
||||
/>
|
||||
{t("duotone")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="btn btn-neutral btn-sm mt-2 w-fit mx-auto"
|
||||
onClick={reset as React.MouseEventHandler<HTMLDivElement>}
|
||||
>
|
||||
{t("reset_defaults")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
ArchivedFormat,
|
||||
} from "@/types/global";
|
||||
import Link from "next/link";
|
||||
import { useSession } from "next-auth/react";
|
||||
import {
|
||||
pdfAvailable,
|
||||
readabilityAvailable,
|
||||
|
@ -17,7 +16,11 @@ import getPublicUserData from "@/lib/client/getPublicUserData";
|
|||
import { useTranslation } from "next-i18next";
|
||||
import { BeatLoader } from "react-spinners";
|
||||
import { useUser } from "@/hooks/store/user";
|
||||
import { useGetLink, useUpdateLink } from "@/hooks/store/links";
|
||||
import {
|
||||
useGetLink,
|
||||
useUpdateLink,
|
||||
useUpdatePreview,
|
||||
} from "@/hooks/store/links";
|
||||
import LinkIcon from "./LinkViews/LinkComponents/LinkIcon";
|
||||
import CopyButton from "./CopyButton";
|
||||
import { useRouter } from "next/router";
|
||||
|
@ -31,6 +34,7 @@ import TagSelection from "./InputSelect/TagSelection";
|
|||
import unescapeString from "@/lib/client/unescapeString";
|
||||
import IconPopover from "./IconPopover";
|
||||
import TextInput from "./TextInput";
|
||||
import usePermissions from "@/hooks/usePermissions";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
|
@ -50,8 +54,13 @@ export default function LinkDetails({
|
|||
const [link, setLink] =
|
||||
useState<LinkIncludingShortenedCollectionAndTags>(activeLink);
|
||||
|
||||
useEffect(() => {
|
||||
setLink(activeLink);
|
||||
}, [activeLink]);
|
||||
|
||||
const permissions = usePermissions(link.collection.id as number);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const session = useSession();
|
||||
const getLink = useGetLink();
|
||||
const { data: user = {} } = useUser();
|
||||
|
||||
|
@ -140,13 +149,14 @@ export default function LinkDetails({
|
|||
clearInterval(interval);
|
||||
}
|
||||
};
|
||||
}, [link?.monolith]);
|
||||
}, [link.monolith]);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
|
||||
|
||||
const updateLink = useUpdateLink();
|
||||
const updatePreview = useUpdatePreview();
|
||||
|
||||
const submit = async (e?: any) => {
|
||||
e?.preventDefault();
|
||||
|
@ -169,7 +179,6 @@ export default function LinkDetails({
|
|||
} else {
|
||||
toast.success(t("updated"));
|
||||
setMode && setMode("view");
|
||||
console.log(data);
|
||||
setLink(data);
|
||||
}
|
||||
},
|
||||
|
@ -208,7 +217,7 @@ export default function LinkDetails({
|
|||
>
|
||||
{previewAvailable(link) ? (
|
||||
<Image
|
||||
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true`}
|
||||
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true&updatedAt=${link.updatedAt}`}
|
||||
width={1280}
|
||||
height={720}
|
||||
alt=""
|
||||
|
@ -227,7 +236,7 @@ export default function LinkDetails({
|
|||
<div className="duration-100 h-40 skeleton rounded-b-none"></div>
|
||||
)}
|
||||
|
||||
{!standalone && (
|
||||
{!standalone && (permissions === true || permissions?.canUpdate) && (
|
||||
<div className="absolute top-0 bottom-0 left-0 right-0 opacity-0 group-hover:opacity-100 duration-100 flex justify-end items-end">
|
||||
<label className="btn btn-xs mb-2 mr-3 opacity-50 hover:opacity-100">
|
||||
{t("upload_preview_image")}
|
||||
|
@ -238,37 +247,26 @@ export default function LinkDetails({
|
|||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
const load = toast.loading(t("updating"));
|
||||
|
||||
const load = toast.loading(t("uploading"));
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/v1/archives/${link.id}/preview`,
|
||||
await updatePreview.mutateAsync(
|
||||
{
|
||||
method: "POST",
|
||||
body: formData,
|
||||
linkId: link.id as number,
|
||||
file,
|
||||
},
|
||||
{
|
||||
onSettled: (data, error) => {
|
||||
toast.dismiss(load);
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
toast.success(t("updated"));
|
||||
setLink({ updatedAt: data.updatedAt, ...link });
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(await res.text());
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
setLink({
|
||||
...link,
|
||||
preview: data.preview,
|
||||
});
|
||||
|
||||
toast.success(t("uploaded"));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
toast.dismiss(load);
|
||||
}
|
||||
}}
|
||||
className="hidden"
|
||||
/>
|
||||
|
@ -277,11 +275,7 @@ export default function LinkDetails({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{standalone ? (
|
||||
<div className="-mt-14 ml-8 relative w-fit pb-2">
|
||||
<LinkIcon link={link} onClick={() => setIconPopover(true)} />
|
||||
</div>
|
||||
) : (
|
||||
{!standalone && (permissions === true || permissions?.canUpdate) ? (
|
||||
<div className="-mt-14 ml-8 relative w-fit pb-2">
|
||||
<div className="tooltip tooltip-bottom" data-tip={t("change_icon")}>
|
||||
<LinkIcon
|
||||
|
@ -316,6 +310,10 @@ export default function LinkDetails({
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="-mt-14 ml-8 relative w-fit pb-2">
|
||||
<LinkIcon link={link} onClick={() => setIconPopover(true)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="max-w-xl sm:px-8 p-5 pb-8 pt-2">
|
||||
|
|
|
@ -49,7 +49,7 @@ export default function LinkActions({
|
|||
await updateLink.mutateAsync(
|
||||
{
|
||||
...link,
|
||||
pinnedBy: isAlreadyPinned ? undefined : [{ id: user.id }],
|
||||
pinnedBy: isAlreadyPinned ? [{ id: undefined }] : [{ id: user.id }],
|
||||
},
|
||||
{
|
||||
onSettled: (data, error) => {
|
||||
|
@ -95,6 +95,9 @@ export default function LinkActions({
|
|||
className={`absolute ${position || "top-3 right-3"} ${
|
||||
alignToTop ? "" : "dropdown-end"
|
||||
} z-20`}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={dropdownTriggerer}
|
||||
onClick={() => setLinkModal(true)}
|
||||
>
|
||||
<div className="btn btn-ghost btn-sm btn-square text-neutral">
|
||||
|
@ -120,7 +123,6 @@ export default function LinkActions({
|
|||
alignToTop ? "" : "translate-y-10"
|
||||
}`}
|
||||
>
|
||||
{(permissions === true || permissions?.canUpdate) && (
|
||||
<li>
|
||||
<div
|
||||
role="button"
|
||||
|
@ -136,7 +138,6 @@ export default function LinkActions({
|
|||
: t("pin_to_dashboard")}
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<div
|
||||
role="button"
|
||||
|
@ -165,7 +166,7 @@ export default function LinkActions({
|
|||
</div>
|
||||
</li>
|
||||
)}
|
||||
{link.type === "url" && (
|
||||
{link.type === "url" && permissions === true && (
|
||||
<li>
|
||||
<div
|
||||
role="button"
|
||||
|
|
|
@ -154,7 +154,7 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
|
|||
<div className="relative rounded-t-2xl h-40 overflow-hidden">
|
||||
{previewAvailable(link) ? (
|
||||
<Image
|
||||
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true`}
|
||||
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true&updatedAt=${link.updatedAt}`}
|
||||
width={1280}
|
||||
height={720}
|
||||
alt=""
|
||||
|
|
|
@ -91,5 +91,3 @@ const LinkPlaceholderIcon = ({
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// `text-black aspect-square text-4xl ${iconClasses}`
|
||||
|
|
|
@ -146,7 +146,7 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
|
|||
<div className="relative rounded-t-2xl overflow-hidden">
|
||||
{previewAvailable(link) ? (
|
||||
<Image
|
||||
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true`}
|
||||
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true&updatedAt=${link.updatedAt}`}
|
||||
width={1280}
|
||||
height={720}
|
||||
alt=""
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
ArchivedFormat,
|
||||
LinkIncludingShortenedCollectionAndTags,
|
||||
} from "@/types/global";
|
||||
import getPublicUserData from "@/lib/client/getPublicUserData";
|
||||
import React, { useState } from "react";
|
||||
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useUser } from "@/hooks/store/user";
|
||||
import { useDeleteLink, useGetLink, useUpdateLink } from "@/hooks/store/links";
|
||||
import { useDeleteLink } from "@/hooks/store/links";
|
||||
import Drawer from "../Drawer";
|
||||
import LinkDetails from "../LinkDetails";
|
||||
import Link from "next/link";
|
||||
|
@ -34,86 +29,6 @@ export default function LinkModal({
|
|||
activeMode,
|
||||
}: 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);
|
||||
|
||||
|
@ -136,6 +51,7 @@ export default function LinkModal({
|
|||
onClick={() => onClose()}
|
||||
></div>
|
||||
|
||||
{(permissions === true || permissions?.canUpdate) && (
|
||||
<div className="flex gap-1 h-8 rounded-full bg-neutral-content bg-opacity-50 text-base-content p-1 text-xs duration-100 select-none z-10">
|
||||
<div
|
||||
className={clsx(
|
||||
|
@ -160,6 +76,7 @@ export default function LinkModal({
|
|||
Edit
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2">
|
||||
<div className={`dropdown dropdown-end z-20`}>
|
||||
|
@ -174,7 +91,7 @@ export default function LinkModal({
|
|||
<ul
|
||||
className={`dropdown-content z-[20] menu shadow bg-base-200 border border-neutral-content rounded-box`}
|
||||
>
|
||||
{(permissions === true || permissions?.canUpdate) && (
|
||||
{
|
||||
<li>
|
||||
<div
|
||||
role="button"
|
||||
|
@ -190,7 +107,7 @@ export default function LinkModal({
|
|||
: t("pin_to_dashboard")}
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
}
|
||||
{link.type === "url" && permissions === true && (
|
||||
<li>
|
||||
<div
|
||||
|
|
|
@ -57,7 +57,7 @@ export default function ViewDropdown({ viewMode, setViewMode }: Props) {
|
|||
>
|
||||
<button
|
||||
onClick={(e) => onChangeViewMode(e, ViewMode.Card)}
|
||||
className={`btn btn-square btn-sm btn-ghost ${
|
||||
className={`btn w-[31%] btn-sm btn-ghost ${
|
||||
viewMode === ViewMode.Card
|
||||
? "bg-primary/20 hover:bg-primary/20"
|
||||
: "hover:bg-neutral/20"
|
||||
|
@ -67,7 +67,7 @@ export default function ViewDropdown({ viewMode, setViewMode }: Props) {
|
|||
</button>
|
||||
<button
|
||||
onClick={(e) => onChangeViewMode(e, ViewMode.Masonry)}
|
||||
className={`btn btn-square btn-sm btn-ghost ${
|
||||
className={`btn w-[31%] btn-sm btn-ghost ${
|
||||
viewMode === ViewMode.Masonry
|
||||
? "bg-primary/20 hover:bg-primary/20"
|
||||
: "hover:bg-neutral/20"
|
||||
|
@ -77,7 +77,7 @@ export default function ViewDropdown({ viewMode, setViewMode }: Props) {
|
|||
</button>
|
||||
<button
|
||||
onClick={(e) => onChangeViewMode(e, ViewMode.List)}
|
||||
className={`btn btn-square btn-sm btn-ghost ${
|
||||
className={`btn w-[31%] btn-sm btn-ghost ${
|
||||
viewMode === ViewMode.List
|
||||
? "bg-primary/20 hover:bg-primary/20"
|
||||
: "hover:bg-neutral/20"
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from "@/types/global";
|
||||
import { useRouter } from "next/router";
|
||||
import { useSession } from "next-auth/react";
|
||||
import Jimp from "jimp";
|
||||
|
||||
const useLinks = (params: LinkRequestQuery = {}) => {
|
||||
const router = useRouter();
|
||||
|
@ -395,6 +396,41 @@ const useUploadFile = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const useUpdatePreview = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ linkId, file }: { linkId: number; file: File }) => {
|
||||
const formBody = new FormData();
|
||||
|
||||
if (!linkId || !file)
|
||||
throw new Error("Error generating preview: Invalid parameters");
|
||||
|
||||
formBody.append("file", file);
|
||||
|
||||
const res = await fetch(
|
||||
`/api/v1/archives/${linkId}?format=` + ArchivedFormat.jpeg,
|
||||
{
|
||||
body: formBody,
|
||||
method: "POST",
|
||||
}
|
||||
);
|
||||
|
||||
const data = res.json();
|
||||
|
||||
return data;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["links"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["dashboardData"] });
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ["collections"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["tags"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["publicLinks"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const useBulkEditLinks = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
@ -479,4 +515,5 @@ export {
|
|||
useGetLink,
|
||||
useBulkEditLinks,
|
||||
resetInfiniteQueryPagination,
|
||||
useUpdatePreview,
|
||||
};
|
||||
|
|
|
@ -25,15 +25,15 @@ export default async function updateLinkById(
|
|||
(e: UsersAndCollections) => e.userId === userId
|
||||
);
|
||||
|
||||
// If the user is able to create a link, they can pin it to their dashboard only.
|
||||
if (canPinPermission) {
|
||||
// If the user is part of a collection, they can pin it to their dashboard
|
||||
if (canPinPermission && data.pinnedBy && data.pinnedBy[0]) {
|
||||
const updatedLink = await prisma.link.update({
|
||||
where: {
|
||||
id: linkId,
|
||||
},
|
||||
data: {
|
||||
pinnedBy:
|
||||
data?.pinnedBy && data.pinnedBy[0]
|
||||
data?.pinnedBy && data.pinnedBy[0].id === userId
|
||||
? { connect: { id: userId } }
|
||||
: { disconnect: { id: userId } },
|
||||
},
|
||||
|
@ -48,7 +48,7 @@ export default async function updateLinkById(
|
|||
},
|
||||
});
|
||||
|
||||
// return { response: updatedLink, status: 200 };
|
||||
return { response: updatedLink, status: 200 };
|
||||
}
|
||||
|
||||
const targetCollectionIsAccessible = await getPermission({
|
||||
|
|
|
@ -105,8 +105,6 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
|||
response: "Collection is not accessible.",
|
||||
});
|
||||
|
||||
// await uploadHandler(linkId, )
|
||||
|
||||
const MAX_LINKS_PER_USER = Number(process.env.MAX_LINKS_PER_USER || 30000);
|
||||
|
||||
const numberOfLinksTheUserHas = await prisma.link.count({
|
||||
|
@ -119,8 +117,7 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
|||
|
||||
if (numberOfLinksTheUserHas > MAX_LINKS_PER_USER)
|
||||
return res.status(400).json({
|
||||
response:
|
||||
"Each collection owner can only have a maximum of ${MAX_LINKS_PER_USER} Links.",
|
||||
response: `Each collection owner can only have a maximum of ${MAX_LINKS_PER_USER} Links.`,
|
||||
});
|
||||
|
||||
const NEXT_PUBLIC_MAX_FILE_BUFFER = Number(
|
||||
|
@ -208,4 +205,94 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
|||
});
|
||||
});
|
||||
}
|
||||
// To update the link preview
|
||||
else if (req.method === "PUT" && format === ArchivedFormat.jpeg) {
|
||||
if (process.env.NEXT_PUBLIC_DEMO === "true")
|
||||
return res.status(400).json({
|
||||
response:
|
||||
"This action is disabled because this is a read-only demo of Linkwarden.",
|
||||
});
|
||||
|
||||
const user = await verifyUser({ req, res });
|
||||
if (!user) return;
|
||||
|
||||
const collectionPermissions = await getPermission({
|
||||
userId: user.id,
|
||||
linkId,
|
||||
});
|
||||
|
||||
if (!collectionPermissions)
|
||||
return res.status(400).json({
|
||||
response: "Collection is not accessible.",
|
||||
});
|
||||
|
||||
const memberHasAccess = collectionPermissions.members.some(
|
||||
(e: UsersAndCollections) => e.userId === user.id && e.canCreate
|
||||
);
|
||||
|
||||
if (!(collectionPermissions.ownerId === user.id || memberHasAccess))
|
||||
return res.status(400).json({
|
||||
response: "Collection is not accessible.",
|
||||
});
|
||||
|
||||
const NEXT_PUBLIC_MAX_FILE_BUFFER = Number(
|
||||
process.env.NEXT_PUBLIC_MAX_FILE_BUFFER || 10
|
||||
);
|
||||
|
||||
const form = formidable({
|
||||
maxFields: 1,
|
||||
maxFiles: 1,
|
||||
maxFileSize: NEXT_PUBLIC_MAX_FILE_BUFFER * 1024 * 1024,
|
||||
});
|
||||
|
||||
form.parse(req, async (err, fields, files) => {
|
||||
const allowedMIMETypes = ["image/png", "image/jpg", "image/jpeg"];
|
||||
|
||||
if (
|
||||
err ||
|
||||
!files.file ||
|
||||
!files.file[0] ||
|
||||
!allowedMIMETypes.includes(files.file[0].mimetype || "")
|
||||
) {
|
||||
// Handle parsing error
|
||||
return res.status(400).json({
|
||||
response: `Sorry, we couldn't process your file. Please ensure it's a PDF, PNG, or JPG format and doesn't exceed ${NEXT_PUBLIC_MAX_FILE_BUFFER}MB.`,
|
||||
});
|
||||
} else {
|
||||
const fileBuffer = fs.readFileSync(files.file[0].filepath);
|
||||
|
||||
if (
|
||||
Buffer.byteLength(fileBuffer) >
|
||||
1024 * 1024 * Number(NEXT_PUBLIC_MAX_FILE_BUFFER)
|
||||
)
|
||||
return res.status(400).json({
|
||||
response: `Sorry, we couldn't process your file. Please ensure it's a PNG, or JPG format and doesn't exceed ${NEXT_PUBLIC_MAX_FILE_BUFFER}MB.`,
|
||||
});
|
||||
|
||||
const linkStillExists = await prisma.link.update({
|
||||
where: { id: linkId },
|
||||
data: {
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
if (linkStillExists) {
|
||||
const collectionId = collectionPermissions.id;
|
||||
createFolder({
|
||||
filePath: `archives/preview/${collectionId}`,
|
||||
});
|
||||
|
||||
generatePreview(fileBuffer, collectionId, linkId);
|
||||
}
|
||||
|
||||
fs.unlinkSync(files.file[0].filepath);
|
||||
|
||||
if (linkStillExists)
|
||||
return res.status(200).json({
|
||||
response: linkStillExists,
|
||||
});
|
||||
else return res.status(400).json({ response: "Link not found." });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -297,7 +297,7 @@
|
|||
"for_collection": "For {{name}}",
|
||||
"create_new_collection": "Create a New Collection",
|
||||
"color": "Color",
|
||||
"reset": "Reset",
|
||||
"reset_defaults": "Reset to Defaults",
|
||||
"updating_collection": "Updating Collection...",
|
||||
"collection_name_placeholder": "e.g. Example Collection",
|
||||
"collection_description_placeholder": "The purpose of this Collection...",
|
||||
|
|
Ŝarĝante…
Reference in New Issue