better link card
This commit is contained in:
parent
98106b9f25
commit
848a33a53e
|
@ -7,7 +7,7 @@ export default function CardView({
|
||||||
links: LinkIncludingShortenedCollectionAndTags[];
|
links: LinkIncludingShortenedCollectionAndTags[];
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="grid 2xl:grid-cols-3 xl:grid-cols-2 grid-cols-1 gap-5">
|
<div className="grid min-[1900px]:grid-cols-4 2xl:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-5">
|
||||||
{links.map((e, i) => {
|
{links.map((e, i) => {
|
||||||
return <LinkCard key={i} link={e} count={i} />;
|
return <LinkCard key={i} link={e} count={i} />;
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
ArchivedFormat,
|
||||||
CollectionIncludingMembersAndLinkCount,
|
CollectionIncludingMembersAndLinkCount,
|
||||||
LinkIncludingShortenedCollectionAndTags,
|
LinkIncludingShortenedCollectionAndTags,
|
||||||
} from "@/types/global";
|
} from "@/types/global";
|
||||||
|
@ -9,8 +10,10 @@ import unescapeString from "@/lib/client/unescapeString";
|
||||||
import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions";
|
import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions";
|
||||||
import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate";
|
import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate";
|
||||||
import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection";
|
import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection";
|
||||||
import LinkIcon from "@/components/LinkViews/LinkComponents/LinkIcon";
|
import Image from "next/image";
|
||||||
|
import { previewAvailable } from "@/lib/shared/getArchiveValidity";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import LinkIcon from "./LinkComponents/LinkIcon";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
link: LinkIncludingShortenedCollectionAndTags;
|
link: LinkIncludingShortenedCollectionAndTags;
|
||||||
|
@ -47,30 +50,48 @@ export default function LinkGrid({ link, count, className }: Props) {
|
||||||
}, [collections, links]);
|
}, [collections, links]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border border-solid border-neutral-content bg-base-200 shadow-md hover:shadow-none duration-100 rounded-2xl relative p-3">
|
<div className="border border-solid border-neutral-content bg-base-200 shadow-md hover:shadow-none duration-100 rounded-2xl relative">
|
||||||
<div
|
<div className="relative rounded-t-2xl h-52">
|
||||||
onClick={() => link.url && window.open(link.url || "", "_blank")}
|
{previewAvailable(link) ? (
|
||||||
className="cursor-pointer"
|
<Image
|
||||||
>
|
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true`}
|
||||||
<LinkIcon link={link} width="w-12 mb-3" />
|
width={1280}
|
||||||
|
height={720}
|
||||||
|
alt=""
|
||||||
|
className="rounded-t-2xl select-none object-cover z-10 h-52 w-full shadow"
|
||||||
|
draggable="false"
|
||||||
|
onError={(e) => {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
target.style.display = "none";
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : undefined}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: "radial-gradient(circle, #ffffff, transparent)",
|
||||||
|
}}
|
||||||
|
className="absolute top-0 left-0 right-0 rounded-t-2xl flex items-center justify-center h-52 shadow rounded-md"
|
||||||
|
>
|
||||||
|
<LinkIcon link={link} width="w-12" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3">
|
||||||
<p className="truncate w-full">
|
<p className="truncate w-full">
|
||||||
{unescapeString(link.name || link.description) || link.url}
|
{unescapeString(link.name || link.description) || link.url}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mt-1 flex flex-col text-xs text-neutral">
|
<div className="mt-1 flex flex-col text-xs text-neutral">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<LinkCollection link={link} collection={collection} />
|
<LinkCollection link={link} collection={collection} />
|
||||||
·
|
·
|
||||||
{link.url ? (
|
{link.url ? (
|
||||||
<div
|
<Link
|
||||||
onClick={(e) => {
|
href={link.url}
|
||||||
e.preventDefault();
|
target="_blank"
|
||||||
window.open(link.url || "", "_blank");
|
|
||||||
}}
|
|
||||||
className="flex items-center hover:opacity-60 cursor-pointer duration-100"
|
className="flex items-center hover:opacity-60 cursor-pointer duration-100"
|
||||||
>
|
>
|
||||||
<p className="truncate">{shortendURL}</p>
|
<p className="truncate">{shortendURL}</p>
|
||||||
</div>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<div className="badge badge-primary badge-sm my-1">
|
<div className="badge badge-primary badge-sm my-1">
|
||||||
{link.type}
|
{link.type}
|
||||||
|
|
|
@ -18,9 +18,11 @@ export default function LinkIcon({
|
||||||
" " +
|
" " +
|
||||||
(width || "w-12");
|
(width || "w-12");
|
||||||
|
|
||||||
|
const [showFavicon, setShowFavicon] = React.useState<boolean>(true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{link.url && url ? (
|
{link.url && url && showFavicon ? (
|
||||||
<Image
|
<Image
|
||||||
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${link.url}&size=32`}
|
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${link.url}&size=32`}
|
||||||
width={64}
|
width={64}
|
||||||
|
@ -28,11 +30,14 @@ export default function LinkIcon({
|
||||||
alt=""
|
alt=""
|
||||||
className={iconClasses}
|
className={iconClasses}
|
||||||
draggable="false"
|
draggable="false"
|
||||||
onError={(e) => {
|
onError={() => {
|
||||||
const target = e.target as HTMLElement;
|
setShowFavicon(false);
|
||||||
target.style.display = "none";
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
) : showFavicon === false ? (
|
||||||
|
<div className="flex items-center justify-center h-12 w-12 bg-base-200 rounded-md shadow">
|
||||||
|
<i className="bi-link-45deg text-4xl text-primary"></i>
|
||||||
|
</div>
|
||||||
) : link.type === "pdf" ? (
|
) : link.type === "pdf" ? (
|
||||||
<i className={`bi-file-earmark-pdf ${iconClasses}`}></i>
|
<i className={`bi-file-earmark-pdf ${iconClasses}`}></i>
|
||||||
) : link.type === "image" ? (
|
) : link.type === "image" ? (
|
||||||
|
|
|
@ -8,6 +8,7 @@ import DOMPurify from "dompurify";
|
||||||
import { Collection, Link, User } from "@prisma/client";
|
import { Collection, Link, User } from "@prisma/client";
|
||||||
import validateUrlSize from "./validateUrlSize";
|
import validateUrlSize from "./validateUrlSize";
|
||||||
import removeFile from "./storage/removeFile";
|
import removeFile from "./storage/removeFile";
|
||||||
|
import Jimp from "jimp";
|
||||||
|
|
||||||
type LinksAndCollectionAndOwner = Link & {
|
type LinksAndCollectionAndOwner = Link & {
|
||||||
collection: Collection & {
|
collection: Collection & {
|
||||||
|
@ -55,6 +56,7 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) {
|
||||||
? "pending"
|
? "pending"
|
||||||
: undefined,
|
: undefined,
|
||||||
readable: !link.readable?.startsWith("archive") ? "pending" : undefined,
|
readable: !link.readable?.startsWith("archive") ? "pending" : undefined,
|
||||||
|
preview: !link.readable?.startsWith("archive") ? "pending" : undefined,
|
||||||
lastPreserved: new Date().toISOString(),
|
lastPreserved: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -65,10 +67,10 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) {
|
||||||
} else if (linkType === "pdf") {
|
} else if (linkType === "pdf") {
|
||||||
await pdfHandler(link); // archive pdf
|
await pdfHandler(link); // archive pdf
|
||||||
return;
|
return;
|
||||||
} else if (user.archiveAsPDF || user.archiveAsScreenshot) {
|
} else if ((user.archiveAsPDF || user.archiveAsScreenshot) && link.url) {
|
||||||
// archive url
|
// archive url
|
||||||
link.url &&
|
|
||||||
(await page.goto(link.url, { waitUntil: "domcontentloaded" }));
|
await page.goto(link.url, { waitUntil: "domcontentloaded" });
|
||||||
|
|
||||||
const content = await page.content();
|
const content = await page.content();
|
||||||
|
|
||||||
|
@ -110,11 +112,81 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Preview
|
||||||
|
|
||||||
|
const ogImageUrl = await page.evaluate(() => {
|
||||||
|
const metaTag = document.querySelector('meta[property="og:image"]');
|
||||||
|
return metaTag ? (metaTag as any).content : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ogImageUrl) {
|
||||||
|
console.log("Found og:image URL:", ogImageUrl);
|
||||||
|
|
||||||
|
// Download the image
|
||||||
|
const imageResponse = await page.goto(ogImageUrl);
|
||||||
|
|
||||||
|
// Check if imageResponse is not null
|
||||||
|
if (imageResponse) {
|
||||||
|
const buffer = await imageResponse.body();
|
||||||
|
|
||||||
|
// Check if buffer is not null
|
||||||
|
if (buffer) {
|
||||||
|
// Load the image using Jimp
|
||||||
|
Jimp.read(buffer, async (err, image) => {
|
||||||
|
if (image) {
|
||||||
|
image?.resize(1280, Jimp.AUTO).quality(20);
|
||||||
|
await image?.writeAsync("og_image.jpg");
|
||||||
|
const processedBuffer = await image?.getBufferAsync(
|
||||||
|
Jimp.MIME_JPEG
|
||||||
|
);
|
||||||
|
|
||||||
|
createFile({
|
||||||
|
data: processedBuffer,
|
||||||
|
filePath: `archives/preview/${link.collectionId}/${link.id}.jpeg`,
|
||||||
|
}).then(() => {
|
||||||
|
return prisma.link.update({
|
||||||
|
where: { id: link.id },
|
||||||
|
data: {
|
||||||
|
preview: `archives/preview/${link.collectionId}/${link.id}.jpeg`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error("Error processing the image:", err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("No image data found.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Image response is null.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("No og:image found");
|
||||||
|
page
|
||||||
|
.screenshot({ type: "jpeg", quality: 20 })
|
||||||
|
.then((screenshot) => {
|
||||||
|
return createFile({
|
||||||
|
data: screenshot,
|
||||||
|
filePath: `archives/preview/${link.collectionId}/${link.id}.jpeg`,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return prisma.link.update({
|
||||||
|
where: { id: link.id },
|
||||||
|
data: {
|
||||||
|
preview: `archives/preview/${link.collectionId}/${link.id}.jpeg`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Screenshot/PDF
|
// Screenshot/PDF
|
||||||
await page.evaluate(
|
await page.evaluate(
|
||||||
autoScroll,
|
autoScroll,
|
||||||
Number(process.env.AUTOSCROLL_TIMEOUT) || 30
|
Number(process.env.AUTOSCROLL_TIMEOUT) || 30
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if the user hasn't deleted the link by the time we're done scrolling
|
// Check if the user hasn't deleted the link by the time we're done scrolling
|
||||||
const linkExists = await prisma.link.findUnique({
|
const linkExists = await prisma.link.findUnique({
|
||||||
where: { id: link.id },
|
where: { id: link.id },
|
||||||
|
@ -176,6 +248,7 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) {
|
||||||
await prisma.link.update({
|
await prisma.link.update({
|
||||||
where: { id: link.id },
|
where: { id: link.id },
|
||||||
data: {
|
data: {
|
||||||
|
lastPreserved: new Date().toISOString(),
|
||||||
readable: !finalLink.readable?.startsWith("archives")
|
readable: !finalLink.readable?.startsWith("archives")
|
||||||
? "unavailable"
|
? "unavailable"
|
||||||
: undefined,
|
: undefined,
|
||||||
|
@ -185,6 +258,9 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) {
|
||||||
pdf: !finalLink.pdf?.startsWith("archives")
|
pdf: !finalLink.pdf?.startsWith("archives")
|
||||||
? "unavailable"
|
? "unavailable"
|
||||||
: undefined,
|
: undefined,
|
||||||
|
preview: !finalLink.preview?.startsWith("archives")
|
||||||
|
? "unavailable"
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
else {
|
else {
|
||||||
|
@ -193,6 +269,9 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) {
|
||||||
removeFile({
|
removeFile({
|
||||||
filePath: `archives/${link.collectionId}/${link.id}_readability.json`,
|
filePath: `archives/${link.collectionId}/${link.id}_readability.json`,
|
||||||
});
|
});
|
||||||
|
removeFile({
|
||||||
|
filePath: `archives/preview/${link.collectionId}/${link.id}.jpeg`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
|
|
|
@ -5,8 +5,8 @@ export default async function getLink(userId: number, query: LinkRequestQuery) {
|
||||||
const POSTGRES_IS_ENABLED = process.env.DATABASE_URL.startsWith("postgresql");
|
const POSTGRES_IS_ENABLED = process.env.DATABASE_URL.startsWith("postgresql");
|
||||||
|
|
||||||
let order: any;
|
let order: any;
|
||||||
if (query.sort === Sort.DateNewestFirst) order = { createdAt: "desc" };
|
if (query.sort === Sort.DateNewestFirst) order = { id: "desc" };
|
||||||
else if (query.sort === Sort.DateOldestFirst) order = { createdAt: "asc" };
|
else if (query.sort === Sort.DateOldestFirst) order = { id: "asc" };
|
||||||
else if (query.sort === Sort.NameAZ) order = { name: "asc" };
|
else if (query.sort === Sort.NameAZ) order = { name: "asc" };
|
||||||
else if (query.sort === Sort.NameZA) order = { name: "desc" };
|
else if (query.sort === Sort.NameZA) order = { name: "desc" };
|
||||||
else if (query.sort === Sort.DescriptionAZ) order = { description: "asc" };
|
else if (query.sort === Sort.DescriptionAZ) order = { description: "asc" };
|
||||||
|
@ -145,7 +145,7 @@ export default async function getLink(userId: number, query: LinkRequestQuery) {
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: order || { createdAt: "desc" },
|
orderBy: order || { id: "desc" },
|
||||||
});
|
});
|
||||||
|
|
||||||
return { response: links, status: 200 };
|
return { response: links, status: 200 };
|
||||||
|
|
|
@ -7,8 +7,8 @@ export default async function getLink(
|
||||||
const POSTGRES_IS_ENABLED = process.env.DATABASE_URL.startsWith("postgresql");
|
const POSTGRES_IS_ENABLED = process.env.DATABASE_URL.startsWith("postgresql");
|
||||||
|
|
||||||
let order: any;
|
let order: any;
|
||||||
if (query.sort === Sort.DateNewestFirst) order = { createdAt: "desc" };
|
if (query.sort === Sort.DateNewestFirst) order = { id: "desc" };
|
||||||
else if (query.sort === Sort.DateOldestFirst) order = { createdAt: "asc" };
|
else if (query.sort === Sort.DateOldestFirst) order = { id: "asc" };
|
||||||
else if (query.sort === Sort.NameAZ) order = { name: "asc" };
|
else if (query.sort === Sort.NameAZ) order = { name: "asc" };
|
||||||
else if (query.sort === Sort.NameZA) order = { name: "desc" };
|
else if (query.sort === Sort.NameZA) order = { name: "desc" };
|
||||||
else if (query.sort === Sort.DescriptionAZ) order = { description: "asc" };
|
else if (query.sort === Sort.DescriptionAZ) order = { description: "asc" };
|
||||||
|
@ -81,7 +81,7 @@ export default async function getLink(
|
||||||
include: {
|
include: {
|
||||||
tags: true,
|
tags: true,
|
||||||
},
|
},
|
||||||
orderBy: order || { createdAt: "desc" },
|
orderBy: order || { id: "desc" },
|
||||||
});
|
});
|
||||||
|
|
||||||
return { response: links, status: 200 };
|
return { response: links, status: 200 };
|
||||||
|
|
|
@ -21,3 +21,12 @@ export function readabilityAvailable(link: any) {
|
||||||
link.readable !== "unavailable"
|
link.readable !== "unavailable"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function previewAvailable(link: any) {
|
||||||
|
return (
|
||||||
|
link &&
|
||||||
|
link.preview &&
|
||||||
|
link.preview !== "pending" &&
|
||||||
|
link.preview !== "unavailable"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -40,6 +40,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",
|
||||||
|
"jimp": "^0.22.10",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
"lottie-web": "^5.12.2",
|
"lottie-web": "^5.12.2",
|
||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
|
@ -53,7 +54,6 @@
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-image-file-resizer": "^0.4.8",
|
"react-image-file-resizer": "^0.4.8",
|
||||||
"react-select": "^5.7.4",
|
"react-select": "^5.7.4",
|
||||||
"sharp": "^0.32.1",
|
|
||||||
"stripe": "^12.13.0",
|
"stripe": "^12.13.0",
|
||||||
"zustand": "^4.3.8"
|
"zustand": "^4.3.8"
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,6 +19,7 @@ export const config = {
|
||||||
export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const linkId = Number(req.query.linkId);
|
const linkId = Number(req.query.linkId);
|
||||||
const format = Number(req.query.format);
|
const format = Number(req.query.format);
|
||||||
|
const isPreview = Boolean(req.query.preview);
|
||||||
|
|
||||||
let suffix: string;
|
let suffix: string;
|
||||||
|
|
||||||
|
@ -55,13 +56,23 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
||||||
.status(401)
|
.status(401)
|
||||||
.json({ response: "You don't have access to this collection." });
|
.json({ response: "You don't have access to this collection." });
|
||||||
|
|
||||||
const { file, contentType, status } = await readFile(
|
if (isPreview) {
|
||||||
`archives/${collectionIsAccessible.id}/${linkId + suffix}`
|
const { file, contentType, status } = await readFile(
|
||||||
);
|
`archives/preview/${collectionIsAccessible.id}/${linkId}.jpeg`
|
||||||
|
);
|
||||||
|
|
||||||
res.setHeader("Content-Type", contentType).status(status as number);
|
res.setHeader("Content-Type", contentType).status(status as number);
|
||||||
|
|
||||||
return res.send(file);
|
return res.send(file);
|
||||||
|
} else {
|
||||||
|
const { file, contentType, status } = await readFile(
|
||||||
|
`archives/${collectionIsAccessible.id}/${linkId + suffix}`
|
||||||
|
);
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", contentType).status(status as number);
|
||||||
|
|
||||||
|
return res.send(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// else if (req.method === "POST") {
|
// else if (req.method === "POST") {
|
||||||
// const user = await verifyUser({ req, res });
|
// const user = await verifyUser({ req, res });
|
||||||
|
|
|
@ -76,6 +76,7 @@ const deleteArchivedFiles = async (link: Link & { collection: Collection }) => {
|
||||||
image: null,
|
image: null,
|
||||||
pdf: null,
|
pdf: null,
|
||||||
readable: null,
|
readable: null,
|
||||||
|
preview: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -88,4 +89,7 @@ const deleteArchivedFiles = async (link: Link & { collection: Collection }) => {
|
||||||
await removeFile({
|
await removeFile({
|
||||||
filePath: `archives/${link.collection.id}/${link.id}_readability.json`,
|
filePath: `archives/${link.collection.id}/${link.id}_readability.json`,
|
||||||
});
|
});
|
||||||
|
await removeFile({
|
||||||
|
filePath: `archives/preview/${link.collection.id}/${link.id}.png`,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,7 +38,7 @@ export default function Collections() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid xl:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-5">
|
<div className="grid min-[1900px]:grid-cols-4 2xl:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-5">
|
||||||
{sortedCollections
|
{sortedCollections
|
||||||
.filter((e) => e.ownerId === data?.user.id)
|
.filter((e) => e.ownerId === data?.user.id)
|
||||||
.map((e, i) => {
|
.map((e, i) => {
|
||||||
|
@ -62,7 +62,7 @@ export default function Collections() {
|
||||||
description={"Shared collections you're a member of"}
|
description={"Shared collections you're a member of"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="grid xl:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-5">
|
<div className="grid min-[1900px]:grid-cols-4 2xl:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-5">
|
||||||
{sortedCollections
|
{sortedCollections
|
||||||
.filter((e) => e.ownerId !== data?.user.id)
|
.filter((e) => e.ownerId !== data?.user.id)
|
||||||
.map((e, i) => {
|
.map((e, i) => {
|
||||||
|
|
|
@ -146,7 +146,7 @@ export default function Dashboard() {
|
||||||
{links[0] ? (
|
{links[0] ? (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div
|
<div
|
||||||
className={`grid 2xl:grid-cols-3 xl:grid-cols-2 grid-cols-1 gap-5 w-full`}
|
className={`grid min-[1900px]:grid-cols-4 2xl:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-5 w-full`}
|
||||||
>
|
>
|
||||||
{links.slice(0, showLinks).map((e, i) => (
|
{links.slice(0, showLinks).map((e, i) => (
|
||||||
<LinkCard key={i} link={e} count={i} />
|
<LinkCard key={i} link={e} count={i} />
|
||||||
|
@ -261,7 +261,7 @@ export default function Dashboard() {
|
||||||
{links.some((e) => e.pinnedBy && e.pinnedBy[0]) ? (
|
{links.some((e) => e.pinnedBy && e.pinnedBy[0]) ? (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div
|
<div
|
||||||
className={`grid 2xl:grid-cols-3 xl:grid-cols-2 grid-cols-1 gap-5 w-full`}
|
className={`grid min-[1900px]:grid-cols-4 2xl:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-5 w-full`}
|
||||||
>
|
>
|
||||||
{links
|
{links
|
||||||
.filter((e) => e.pinnedBy && e.pinnedBy[0])
|
.filter((e) => e.pinnedBy && e.pinnedBy[0])
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
|
@ -60,7 +60,7 @@ async function processBatch() {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
take: archiveTakeCount,
|
take: archiveTakeCount,
|
||||||
orderBy: { createdAt: "asc" },
|
orderBy: { id: "asc" },
|
||||||
include: {
|
include: {
|
||||||
collection: {
|
collection: {
|
||||||
include: {
|
include: {
|
||||||
|
@ -114,10 +114,17 @@ async function processBatch() {
|
||||||
{
|
{
|
||||||
readable: "pending",
|
readable: "pending",
|
||||||
},
|
},
|
||||||
|
///////////////////////
|
||||||
|
{
|
||||||
|
preview: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
preview: "pending",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
take: archiveTakeCount,
|
take: archiveTakeCount,
|
||||||
orderBy: { createdAt: "desc" },
|
orderBy: { id: "desc" },
|
||||||
include: {
|
include: {
|
||||||
collection: {
|
collection: {
|
||||||
include: {
|
include: {
|
||||||
|
|
Ŝarĝante…
Reference in New Issue