diff --git a/components/LinkViews/Layouts/CardView.tsx b/components/LinkViews/Layouts/CardView.tsx index 0cc0db1..5d807cd 100644 --- a/components/LinkViews/Layouts/CardView.tsx +++ b/components/LinkViews/Layouts/CardView.tsx @@ -7,7 +7,7 @@ export default function CardView({ links: LinkIncludingShortenedCollectionAndTags[]; }) { return ( -
+
{links.map((e, i) => { return ; })} diff --git a/components/LinkViews/LinkCard.tsx b/components/LinkViews/LinkCard.tsx index 3956070..cb9eb1f 100644 --- a/components/LinkViews/LinkCard.tsx +++ b/components/LinkViews/LinkCard.tsx @@ -3,7 +3,7 @@ import { CollectionIncludingMembersAndLinkCount, LinkIncludingShortenedCollectionAndTags, } from "@/types/global"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import useLinkStore from "@/store/links"; import useCollectionStore from "@/store/collections"; import unescapeString from "@/lib/client/unescapeString"; @@ -14,6 +14,8 @@ import Image from "next/image"; import { previewAvailable } from "@/lib/shared/getArchiveValidity"; import Link from "next/link"; import LinkIcon from "./LinkComponents/LinkIcon"; +import LinkGroupedIconURL from "./LinkComponents/LinkGroupedIconURL"; +import useOnScreen from "@/hooks/useOnScreen"; type Props = { link: LinkIncludingShortenedCollectionAndTags; @@ -24,7 +26,7 @@ type Props = { export default function LinkGrid({ link, count, className }: Props) { const { collections } = useCollectionStore(); - const { links } = useLinkStore(); + const { links, getLink } = useLinkStore(); let shortendURL; @@ -49,79 +51,133 @@ export default function LinkGrid({ link, count, className }: Props) { ); }, [collections, links]); + const ref = useRef(null); + const isVisible = useOnScreen(ref); + + useEffect(() => { + let interval: any; + + if ( + isVisible && + !link.preview?.startsWith("archives") && + link.preview !== "unavailable" + ) { + interval = setInterval(async () => { + getLink(link.id as number); + }, 5000); + } + + return () => { + if (interval) { + clearInterval(interval); + } + }; + }, [isVisible]); + + const [showInfo, setShowInfo] = useState(false); + return ( -
-
+
+
{previewAvailable(link) ? ( { const target = e.target as HTMLElement; target.style.display = "none"; }} /> - ) : undefined} + ) : ( +
+ )}
- +
-
-

+

+

{unescapeString(link.name || link.description) || link.url}

-
-
+ + +
+ +

{shortendURL}

+
+ + +
+ +
+
- · - {link.url ? ( - -

{shortendURL}

- - ) : ( -
- {link.type} -
- )}
- {/*

{unescapeString(link.description)}

- {link.tags[0] ? ( -
-
- {link.tags.map((e, i) => ( - { - e.stopPropagation(); - }} - className="btn btn-xs btn-ghost truncate max-w-[19rem]" - > - #{e.name} - - ))} -
-
- ) : undefined} */}
- + {showInfo ? ( +
+
setShowInfo(!showInfo)} + className=" float-right btn btn-sm outline-none btn-circle btn-ghost z-10" + > + +
+
+

{unescapeString(link.description)}

+ {link.tags[0] ? ( +
+
+ {link.tags.map((e, i) => ( + { + e.stopPropagation(); + }} + className="btn btn-xs btn-ghost truncate max-w-[19rem]" + > + #{e.name} + + ))} +
+
+ ) : undefined} +
+
+ ) : undefined} + + setShowInfo(!showInfo)} + linkInfo={showInfo} + />
); } diff --git a/components/LinkViews/LinkComponents/LinkActions.tsx b/components/LinkViews/LinkComponents/LinkActions.tsx index 0c4adf1..e3eb4b8 100644 --- a/components/LinkViews/LinkComponents/LinkActions.tsx +++ b/components/LinkViews/LinkComponents/LinkActions.tsx @@ -15,9 +15,16 @@ type Props = { link: LinkIncludingShortenedCollectionAndTags; collection: CollectionIncludingMembersAndLinkCount; position?: string; + toggleShowInfo: () => void; + linkInfo: boolean; }; -export default function LinkActions({ link, collection, position }: Props) { +export default function LinkActions({ + link, + toggleShowInfo, + position, + linkInfo, +}: Props) { const permissions = usePermissions(link.collection.id as number); const [editLinkModal, setEditLinkModal] = useState(false); @@ -85,6 +92,18 @@ export default function LinkActions({ link, collection, position }: Props) {
) : undefined} +
  • +
    { + (document?.activeElement as HTMLElement)?.blur(); + toggleShowInfo(); + }} + > + {!linkInfo ? "Show" : "Hide"} Link Info +
    +
  • {permissions === true || permissions?.canUpdate ? (
  • { (document?.activeElement as HTMLElement)?.blur(); setPreservedFormatsModal(true); - // updateArchive(); }} > Preserved Formats diff --git a/components/LinkViews/LinkComponents/LinkCollection.tsx b/components/LinkViews/LinkComponents/LinkCollection.tsx index b6e32e9..7ca8ad1 100644 --- a/components/LinkViews/LinkComponents/LinkCollection.tsx +++ b/components/LinkViews/LinkComponents/LinkCollection.tsx @@ -21,6 +21,7 @@ export default function LinkCollection({ router.push(`/collections/${link.collection.id}`); }} className="flex items-center gap-1 max-w-full w-fit hover:opacity-70 duration-100" + title={collection?.name} > (true); + + let shortendURL; + + try { + shortendURL = new URL(link.url || "").host.toLowerCase(); + } catch (error) { + console.log(error); + } + + return ( + +
    + {link.url && url && showFavicon ? ( + { + setShowFavicon(false); + }} + /> + ) : showFavicon === false ? ( + + ) : link.type === "pdf" ? ( + + ) : link.type === "image" ? ( + + ) : undefined} +

    +

    {shortendURL}

    +

    +
    + + ); +} diff --git a/components/LinkViews/LinkComponents/LinkIcon.tsx b/components/LinkViews/LinkComponents/LinkIcon.tsx index 118a196..e5f7380 100644 --- a/components/LinkViews/LinkComponents/LinkIcon.tsx +++ b/components/LinkViews/LinkComponents/LinkIcon.tsx @@ -14,35 +14,31 @@ export default function LinkIcon({ isValidUrl(link.url || "") && link.url ? new URL(link.url) : undefined; const iconClasses: string = - "bg-white text-primary shadow rounded-md border-[2px] border-white select-none z-10" + + "bg-white shadow rounded-md border-[2px] flex item-center justify-center border-white select-none z-10" + " " + (width || "w-12"); const [showFavicon, setShowFavicon] = React.useState(true); - return ( -
    - {link.url && url && showFavicon ? ( - { - setShowFavicon(false); - }} - /> - ) : showFavicon === false ? ( -
    - -
    - ) : link.type === "pdf" ? ( - - ) : link.type === "image" ? ( - - ) : undefined} + return link.url && url && showFavicon ? ( + { + setShowFavicon(false); + }} + /> + ) : showFavicon === false ? ( +
    +
    - ); + ) : link.type === "pdf" ? ( + + ) : link.type === "image" ? ( + + ) : undefined; } diff --git a/components/LinkViews/LinkList.tsx b/components/LinkViews/LinkList.tsx index 586b384..277201c 100644 --- a/components/LinkViews/LinkList.tsx +++ b/components/LinkViews/LinkList.tsx @@ -10,6 +10,7 @@ import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions"; import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate"; import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection"; import LinkIcon from "@/components/LinkViews/LinkComponents/LinkIcon"; +import Link from "next/link"; type Props = { link: LinkIncludingShortenedCollectionAndTags; @@ -45,6 +46,8 @@ export default function LinkCardCompact({ link, count, className }: Props) { ); }, [collections, links]); + const [showInfo, setShowInfo] = useState(false); + return ( <>
    @@ -57,7 +60,7 @@ export default function LinkCardCompact({ link, count, className }: Props) {
    -

    +

    {unescapeString(link.name || link.description) || link.url}

    @@ -86,7 +89,34 @@ export default function LinkCardCompact({ link, count, className }: Props) { link={link} collection={collection} position="top-3 right-0 sm:right-3" + toggleShowInfo={() => setShowInfo(!showInfo)} + linkInfo={showInfo} /> + {showInfo ? ( +
    +
    +

    {unescapeString(link.description)}

    + {link.tags[0] ? ( +
    +
    + {link.tags.map((e, i) => ( + { + e.stopPropagation(); + }} + className="btn btn-xs btn-ghost truncate max-w-[19rem]" + > + #{e.name} + + ))} +
    +
    + ) : undefined} +
    +
    + ) : undefined}
    diff --git a/components/Modal.tsx b/components/Modal.tsx index ac74785..c68b2e0 100644 --- a/components/Modal.tsx +++ b/components/Modal.tsx @@ -16,7 +16,7 @@ export default function Modal({ toggleModal, className, children }: Props) { }); return ( -
    +
    {sidebar ? ( -
    +
    diff --git a/hooks/useOnScreen.tsx b/hooks/useOnScreen.tsx new file mode 100644 index 0000000..6481e68 --- /dev/null +++ b/hooks/useOnScreen.tsx @@ -0,0 +1,20 @@ +import { RefObject, useEffect, useMemo, useState } from "react"; + +export default function useOnScreen(ref: RefObject) { + const [isIntersecting, setIntersecting] = useState(false); + + const observer = useMemo( + () => + new IntersectionObserver(([entry]) => + setIntersecting(entry.isIntersecting) + ), + [ref] + ); + + useEffect(() => { + observer.observe(ref.current as HTMLElement); + return () => observer.disconnect(); + }, []); + + return isIntersecting; +} diff --git a/lib/api/archiveHandler.ts b/lib/api/archiveHandler.ts index 14435d5..4bab749 100644 --- a/lib/api/archiveHandler.ts +++ b/lib/api/archiveHandler.ts @@ -61,13 +61,13 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) { }, }); - if (linkType === "image") { + if (linkType === "image" && !link.image?.startsWith("archive")) { await imageHandler(link, imageExtension); // archive image (jpeg/png) return; - } else if (linkType === "pdf") { + } else if (linkType === "pdf" && !link.pdf?.startsWith("archive")) { await pdfHandler(link); // archive pdf return; - } else if ((user.archiveAsPDF || user.archiveAsScreenshot) && link.url) { + } else if (link.url) { // archive url await page.goto(link.url, { waitUntil: "domcontentloaded" }); @@ -97,7 +97,11 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) { const articleText = article?.textContent .replace(/ +(?= )/g, "") // strip out multiple spaces .replace(/(\r\n|\n|\r)/gm, " "); // strip out line breaks - if (articleText && articleText !== "") { + if ( + articleText && + articleText !== "" && + !link.readable?.startsWith("archive") + ) { await createFile({ data: JSON.stringify(article), filePath: `archives/${targetLink.collectionId}/${link.id}_readability.json`, @@ -126,7 +130,7 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) { const imageResponse = await page.goto(ogImageUrl); // Check if imageResponse is not null - if (imageResponse) { + if (imageResponse && !link.preview?.startsWith("archive")) { const buffer = await imageResponse.body(); // Check if buffer is not null @@ -135,7 +139,6 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) { 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 ); @@ -161,7 +164,9 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) { } else { console.log("Image response is null."); } - } else { + + await page.goBack(); + } else if (!link.preview?.startsWith("archive")) { console.log("No og:image found"); page .screenshot({ type: "jpeg", quality: 20 }) @@ -194,7 +199,7 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) { if (linkExists) { const processingPromises = []; - if (user.archiveAsScreenshot) { + if (user.archiveAsScreenshot && !link.image?.startsWith("archive")) { processingPromises.push( page.screenshot({ fullPage: true }).then((screenshot) => { return createFile({ @@ -204,7 +209,7 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) { }) ); } - if (user.archiveAsPDF) { + if (user.archiveAsPDF && !link.pdf?.startsWith("archive")) { processingPromises.push( page .pdf({ diff --git a/pages/dashboard.tsx b/pages/dashboard.tsx index 88a0662..f84f126 100644 --- a/pages/dashboard.tsx +++ b/pages/dashboard.tsx @@ -146,7 +146,7 @@ export default function Dashboard() { {links[0] ? (
    {links.slice(0, showLinks).map((e, i) => ( @@ -261,7 +261,7 @@ export default function Dashboard() { {links.some((e) => e.pinnedBy && e.pinnedBy[0]) ? (
    {links .filter((e) => e.pinnedBy && e.pinnedBy[0]) diff --git a/styles/globals.css b/styles/globals.css index 35948bb..6b1c155 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -56,7 +56,7 @@ body { } .fade-in { - animation: fade-in-animation 100ms; + animation: fade-in-animation 200ms; } @keyframes fade-in-animation {